• 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.Context;
23 import android.media.AudioDeviceCallback;
24 import android.media.AudioDeviceInfo;
25 import android.media.AudioManager;
26 import android.media.MediaRecorder;
27 import android.mediapc.cts.common.PerformanceClassEvaluator;
28 import android.mediapc.cts.common.Requirements;
29 import android.mediapc.cts.common.Requirements.RoundTripAudioLatencyRequirement;
30 import android.mediapc.cts.common.Requirements.TwentyFourBitAudioRequirement;
31 import android.os.Build;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Message;
35 import android.os.SystemProperties;
36 import android.util.Log;
37 import android.view.View;
38 import android.view.View.OnClickListener;
39 import android.webkit.WebView;
40 import android.widget.Button;
41 import android.widget.LinearLayout;
42 import android.widget.ProgressBar;
43 import android.widget.TextView;
44 
45 import com.android.compatibility.common.util.CddTest;
46 import com.android.compatibility.common.util.ResultType;
47 import com.android.compatibility.common.util.ResultUnit;
48 import com.android.cts.verifier.CtsVerifierReportLog;
49 import com.android.cts.verifier.PassFailButtons;
50 import com.android.cts.verifier.R;
51 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils;
52 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils.UsbDeviceReport;
53 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
54 import com.android.cts.verifier.audio.audiolib.AudioUtils;
55 import com.android.cts.verifier.audio.audiolib.DisplayUtils;
56 import com.android.cts.verifier.audio.audiolib.StatUtils;
57 import com.android.cts.verifier.audio.audiolib.WavFileCapture;
58 import com.android.cts.verifier.libs.ui.HtmlFormatter;
59 import com.android.cts.verifier.libs.ui.TextFormatter;
60 
61 import org.hyphonate.megaaudio.common.Globals;
62 import org.hyphonate.megaaudio.common.StreamBase;
63 import org.json.JSONArray;
64 import org.json.JSONException;
65 import org.json.JSONObject;
66 import org.junit.rules.TestName;
67 
68 import java.io.File;
69 import java.util.Locale;
70 
71 /**
72  * CtsVerifier Audio Loopback Latency Test
73  */
74 @CddTest(requirements = {"5.10/C-1-2,C-1-5", "5.6/H-1-2", "5.6/H-1-3"})
75 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
76     private static final String TAG = "AudioLoopbackLatencyActivity";
77     private static final boolean LOG = false;
78 
79     // JNI load
80     static {
81         try {
82             System.loadLibrary("audioloopback_jni");
83         } catch (UnsatisfiedLinkError e) {
84             Log.e(TAG, "Error loading Audio Loopback JNI library");
85             Log.e(TAG, "e: " + e);
86             e.printStackTrace();
87         }
88 
89         /* TODO: gracefully fail/notify if the library can't be loaded */
90     }
91 
92     Context mContext;
93     protected AudioManager mAudioManager;
94 
95     private ConnectListener mConnectListener;
96 
97     // UI
98     TextView[] mRouteStatus = new TextView[NUM_TEST_ROUTES];
99 
100     TextView mTestStatusText;
101     ProgressBar mProgressBar;
102     int mMaxLevel;
103 
104     TextView mTestInstructions;
105 
106     protected AudioLoopbackUtilitiesHandler mUtiltitiesHandler;
107 
108     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
109     private Button[] mStartButtons = new Button[NUM_TEST_ROUTES];
110 
111     private WebView mResultsWebView;
112 
113     String mYesString;
114     String mNoString;
115 
116     String mPassString;
117     String mFailString;
118     String mNotTestedString;
119     String mRequiredString;
120     String mNoHardwareString;
121     String mUnknownHardwareString;
122 
123     // These flags determine the maximum allowed latency
124     private boolean mClaimsProAudio;
125     private boolean mClaimsLowLatency;
126     private boolean mClaimsMediaPerformance;
127     private boolean mClaimsOutput;
128     private boolean mClaimsInput;
129 
130     // Useful info
131     int mFirstProductApiLevel = SystemProperties.getInt("ro.product.first_api_level", -1);
132     int mFirstBoardApiLevel = SystemProperties.getInt("ro.board.first_api_level", -1);
133     private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
134     private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
135     private boolean mOverallPass = false;
136 
137     private boolean mIsWatch;
138     private boolean mIsTV;
139     private boolean mIsAutomobile;
140     private boolean mIsHandheld;
141     private boolean mIsEmulator;
142     private int     mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
143     private int     mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
144 
145     private int mUSBAudioSupport;
146     private int mAnalogJackSupport;
147 
148     // Peripheral(s)
149     private static final int NUM_TEST_ROUTES =       3;
150     private static final int TESTROUTE_DEVICE =      0; // device speaker + mic
151     private static final int TESTROUTE_ANALOG_JACK = 1;
152     private static final int TESTROUTE_USB =         2;
153     private int mTestRoute = TESTROUTE_DEVICE;
154 
155     // Loopback Logic
156     private NativeAnalyzerThread mNativeAnalyzerThread = null;
157 
158     protected static final int NUM_TEST_PHASES = 5;
159     protected int mTestPhase = 0;
160 
161     private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6;
162     private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6;
163 
164 
165 
166     public static final double LATENCY_NOT_MEASURED = 0.0;
167     public static final double LATENCY_BASIC = 200.0; // Was 300 in CDD 14 for UDC
168                                                       // Was 250 in CDD 15 for VIC
169     public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0;
170     public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0;
171     public static final double LATENCY_PRO_AUDIO_USB = 25.0;
172     public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0;
173 
174     public static final double TIMESTAMP_ACCURACY_MS = 30.0;
175 
176     // The audio stream callback threads should stop and close
177     // in less than a few hundred msec. This is a generous timeout value.
178     private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
179 
180     private static final String LOG_ERROR_STR = "Could not log metric.";
181 
182     private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES];
183     private volatile UsbDeviceReport mUsbDeviceReport;
184 
185     final TestName mTestName = new TestName();
186 
187     // WAV File Stuff
188     File mFilesDir;
189     WavFileCapture mWavFileCapture;
190 
191     class TestSpec {
192         private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec";
193         // impossibly low latencies (indicating something in the test went wrong).
194         protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0;
195 
196         final int mRouteId;
197 
198         // runtime assigned device ID
199         static final int DEVICEID_NONE = -1;
200         int mInputDeviceId;
201         int mOutputDeviceId;
202 
203         String mDeviceName;
204 
205         double[] mLatencyMS = new double[NUM_TEST_PHASES];
206         double[] mConfidence = new double[NUM_TEST_PHASES];
207 
208         double[] mTimestampLatencyMS = new double[NUM_TEST_PHASES];
209 
210         double mMeanLatencyMS;
211         double mMeasuredLatencyMS;
212         double mLatencyOffsetMS;
213         double mMeanAbsoluteDeviation;
214         double mMeanConfidence;
215         double mRequiredConfidence;
216         double mMeanTimestampLatencyMS;
217         int mSampleRate;
218         boolean mHas24BitHardwareSupport;
219         int mHardwareFormat;
220 
221         // Stream Attributes
222         int[] mBurstFrames = new int[NativeAnalyzerThread.NUM_STREAM_TYPES];
223         int[] mCapacityFrames = new int[NativeAnalyzerThread.NUM_STREAM_TYPES];
224         boolean[] mIsLowLatencyStream = new boolean[NativeAnalyzerThread.NUM_STREAM_TYPES];
225         boolean[] mIsMMapStream = new boolean[NativeAnalyzerThread.NUM_STREAM_TYPES];
226 
227         boolean mRouteAvailable; // Have we seen this route/device at any time
228         boolean mRouteConnected; // is the route available NOW
229         boolean mTestRun;
230 
231         String mWavCaptureFileName;
232         int mCaptureCode = mWavFileCapture.CAPTURE_NOTDONE;
233 
TestSpec(int routeId, double requiredConfidence)234         TestSpec(int routeId, double requiredConfidence) {
235             mRouteId = routeId;
236             mRequiredConfidence = requiredConfidence;
237 
238             mInputDeviceId = DEVICEID_NONE;
239             mOutputDeviceId = DEVICEID_NONE;
240 
241             // Default to true if test not run.
242             mHas24BitHardwareSupport = true;
243         }
244 
startTest()245         void startTest() {
246             mTestRun = true;
247 
248             java.util.Arrays.fill(mLatencyMS, 0.0);
249             java.util.Arrays.fill(mConfidence, 0.0);
250             java.util.Arrays.fill(mTimestampLatencyMS, 0.0);
251 
252             mTestStatusText.setVisibility(View.VISIBLE);
253             mProgressBar.setVisibility(View.VISIBLE);
254 
255             // WAV Capture
256             mWavCaptureFileName = "AudioLoopbackTest_" + mRouteId + ".wav";
257             mWavFileCapture.setCaptureFile(mFilesDir.getPath() + "/" + mWavCaptureFileName);
258             mWavFileCapture.setWavSpec(/*numChannels*/ 1, mSampleRate);
259             mWavFileCapture.startCapture();
260         }
261 
recordPhase(int phase, double latencyMS, double confidence, double timestampLatencyMS)262         void recordPhase(int phase, double latencyMS, double confidence,
263                 double timestampLatencyMS) {
264             mLatencyMS[phase] = latencyMS;
265             mConfidence[phase] = confidence;
266             mTimestampLatencyMS[phase] = timestampLatencyMS;
267         }
268 
handleTestCompletion()269         void handleTestCompletion() {
270             // Some USB devices have higher latency than the Google device.
271             // We subtract that extra latency so that we are just using the Android device latency.
272             mMeasuredLatencyMS = StatUtils.calculateMean(mLatencyMS);
273             mLatencyOffsetMS = 0.0;
274             if (mRouteId == TESTROUTE_USB) {
275                 UsbDeviceReport report = mUsbDeviceReport; // fetch volatile object
276                 if (report != null && report.isValid) {
277                     mLatencyOffsetMS = report.latencyOffset;
278                 }
279             }
280             mMeanLatencyMS = mMeasuredLatencyMS - mLatencyOffsetMS;
281 
282             mMeanAbsoluteDeviation =
283                     StatUtils.calculateMeanAbsoluteDeviation(
284                             mMeanLatencyMS, mLatencyMS, mLatencyMS.length);
285             mMeanConfidence = StatUtils.calculateMean(mConfidence);
286             mMeanTimestampLatencyMS = StatUtils.calculateMean(mTimestampLatencyMS);
287             if (mNativeAnalyzerThread != null) {
288                 // Get Stream Attributes
289                 mSampleRate = mNativeAnalyzerThread.getSampleRate();
290                 mHas24BitHardwareSupport = mNativeAnalyzerThread.has24BitHardwareSupport();
291                 mHardwareFormat = mNativeAnalyzerThread.getHardwareFormat();
292 
293                 // direction-dependent
294                 int[] directions = new int[] {NativeAnalyzerThread.STREAM_INPUT,
295                         NativeAnalyzerThread.STREAM_OUTPUT};
296                 for (int direction : directions) {
297                     mIsLowLatencyStream[direction] =
298                             mNativeAnalyzerThread.isLowLatencyStream(direction);
299 
300                     mBurstFrames[direction] =
301                             mNativeAnalyzerThread.getBurstFrames(direction);
302 
303                     mCapacityFrames[direction] =
304                             mNativeAnalyzerThread.getCapacityFrames(direction);
305 
306                     mIsMMapStream[direction] =
307                             mNativeAnalyzerThread.isMMapStream(direction);
308                 }
309             }
310 
311             mTestStatusText.setVisibility(View.GONE);
312             mProgressBar.setVisibility(View.GONE);
313 
314             mCaptureCode = mWavFileCapture.completeCapture();
315         }
316 
isMeasurementValid()317         boolean isMeasurementValid() {
318             return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence;
319         }
320 
has24BitHardwareSupport()321         boolean has24BitHardwareSupport() {
322             return mHas24BitHardwareSupport;
323         }
324 
getResultString()325         String getResultString() {
326             String result;
327 
328             if (!mRouteAvailable) {
329                 result = getString(R.string.audio_loopback_routenotavailable);
330             } else if (!mTestRun) {
331                 result = getString(R.string.audio_loopback_testnotrun);
332             } else if (mMeanConfidence < mRequiredConfidence) {
333                 result = String.format(getString(R.string.audio_loopback_insufficientconfidence),
334                         mMeanConfidence, mRequiredConfidence);
335             } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) {
336                 result = String.format(getString(R.string.audio_loopback_latencytoolow),
337                         mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS);
338             } else {
339                 // Print more info if we are correcting the latency measurement.
340                 String adjustment = "";
341                 if (mLatencyOffsetMS > 0.0) {
342                     adjustment = String.format(Locale.US, "Measured Latency: %.2f ms\n"
343                             + "Latency Offset: %.2f ms\n",
344                         mMeasuredLatencyMS,
345                         mLatencyOffsetMS);
346 
347                 }
348                 result = String.format(Locale.US,
349                         "Test Finished\nMean Latency: %.2f ms\n"
350                             + "%s"
351                             + "Mean Absolute Deviation: %.2f\n"
352                             + "Confidence: %.2f\n"
353                             + "Low Latency Path: [out:%s, in:%s]\n"
354                             + "24 Bit Hardware Support: %s\n"
355                             + "Timestamp Latency:%.2f ms",
356                         mMeanLatencyMS,
357                         adjustment,
358                         mMeanAbsoluteDeviation,
359                         mMeanConfidence,
360                         mIsLowLatencyStream[NativeAnalyzerThread.STREAM_OUTPUT]
361                                 ? mYesString : mNoString,
362                         mIsLowLatencyStream[NativeAnalyzerThread.STREAM_INPUT]
363                                 ? mYesString : mNoString,
364                         mHas24BitHardwareSupport ? mYesString : mNoString,
365                         mMeanTimestampLatencyMS);
366             }
367 
368             return result;
369         }
370 
371         // ReportLog Schema (per route)
372         private static final String KEY_ROUTEINDEX = "route_index";
373         private static final String KEY_LATENCY = "latency";
374         private static final String KEY_CONFIDENCE = "confidence";
375         private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation";
376         private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
377         private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
378         private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
379         private static final String KEY_TEST_PERIPHERAL_NAME = "test_peripheral_name";
380         private static final String KEY_TIMESTAMP_LATENCY = "timestamp_latency";
381         private static final String KEY_SAMPLE_RATE = "sample_rate";
382         private static final String KEY_HAS_24_BIT_HARDWARE_SUPPORT =
383                 "has_24_bit_hardware_support";
384         private static final String KEY_HARDWARE_FORMAT = "hardware_format";
385         // key for output low-latency. Pre-existing key, don't change
386         private static final String KEY_OUTPUT_IS_LOW_LATENCY = "is_low_latency";
387         private static final String KEY_INPUT_IS_LOW_LATENCY = "input_is_low_latency";
388         private static final String KEY_OUTPUT_BURST_FRAMES = "output_burst_frames";
389         private static final String KEY_INPUT_BURST_FRAMES = "input_burst_frames";
390         private static final String KEY_OUTPUT_CAPACITY_FRAMES = "output_capacity_frames";
391         private static final String KEY_INPUT_CAPACITY_FRAMES = "input_capacity_frames";
392         private static final String KEY_OUTPUT_IS_MMAP_STREAM = "output_is_mmap_stream";
393         private static final String KEY_INPUT_IS_MMAP_STREAM = "input_is_mmap_stream";
394 
recordTestResults(CtsVerifierReportLog reportLog)395         void recordTestResults(CtsVerifierReportLog reportLog) {
396             reportLog.addValue(
397                     KEY_ROUTEINDEX,
398                     mRouteId,
399                     ResultType.NEUTRAL,
400                     ResultUnit.NONE);
401 
402             reportLog.addValue(
403                     KEY_LATENCY,
404                     mMeanLatencyMS,
405                     ResultType.LOWER_BETTER,
406                     ResultUnit.MS);
407 
408             reportLog.addValue(
409                     KEY_CONFIDENCE,
410                     mMeanConfidence,
411                     ResultType.HIGHER_BETTER,
412                     ResultUnit.NONE);
413 
414             reportLog.addValue(
415                     KEY_MEANABSDEVIATION,
416                     mMeanAbsoluteDeviation,
417                     ResultType.NEUTRAL,
418                     ResultUnit.NONE);
419 
420             reportLog.addValue(
421                     KEY_TEST_PERIPHERAL_NAME,
422                     mDeviceName,
423                     ResultType.NEUTRAL,
424                     ResultUnit.NONE);
425 
426             reportLog.addValue(
427                     KEY_TIMESTAMP_LATENCY,
428                     mMeanTimestampLatencyMS,
429                     ResultType.NEUTRAL,
430                     ResultUnit.NONE);
431 
432             reportLog.addValue(
433                     KEY_SAMPLE_RATE,
434                     mSampleRate,
435                     ResultType.NEUTRAL,
436                     ResultUnit.NONE);
437 
438             reportLog.addValue(
439                     KEY_HAS_24_BIT_HARDWARE_SUPPORT,
440                     mHas24BitHardwareSupport,
441                     ResultType.NEUTRAL,
442                     ResultUnit.NONE);
443 
444             reportLog.addValue(
445                     KEY_HARDWARE_FORMAT,
446                     mHardwareFormat,
447                     ResultType.NEUTRAL,
448                     ResultUnit.NONE);
449 
450             reportLog.addValue(
451                     KEY_OUTPUT_IS_LOW_LATENCY,
452                     mIsLowLatencyStream[NativeAnalyzerThread.STREAM_OUTPUT],
453                     ResultType.NEUTRAL,
454                     ResultUnit.NONE);
455 
456             reportLog.addValue(
457                     KEY_INPUT_IS_LOW_LATENCY,
458                     mIsLowLatencyStream[NativeAnalyzerThread.STREAM_INPUT],
459                     ResultType.NEUTRAL,
460                     ResultUnit.NONE);
461 
462             reportLog.addValue(
463                     KEY_OUTPUT_BURST_FRAMES,
464                     mBurstFrames[NativeAnalyzerThread.STREAM_OUTPUT],
465                     ResultType.NEUTRAL,
466                     ResultUnit.NONE);
467 
468             reportLog.addValue(
469                     KEY_INPUT_BURST_FRAMES,
470                     mBurstFrames[NativeAnalyzerThread.STREAM_INPUT],
471                     ResultType.NEUTRAL,
472                     ResultUnit.NONE);
473 
474             reportLog.addValue(
475                     KEY_OUTPUT_CAPACITY_FRAMES,
476                     mCapacityFrames[NativeAnalyzerThread.STREAM_OUTPUT],
477                     ResultType.NEUTRAL,
478                     ResultUnit.NONE);
479 
480             reportLog.addValue(
481                     KEY_INPUT_CAPACITY_FRAMES,
482                     mCapacityFrames[NativeAnalyzerThread.STREAM_INPUT],
483                     ResultType.NEUTRAL,
484                     ResultUnit.NONE);
485 
486             reportLog.addValue(
487                     KEY_OUTPUT_IS_MMAP_STREAM,
488                     mIsMMapStream[NativeAnalyzerThread.STREAM_OUTPUT],
489                     ResultType.NEUTRAL,
490                     ResultUnit.NONE);
491 
492             reportLog.addValue(
493                     KEY_INPUT_IS_MMAP_STREAM,
494                     mIsMMapStream[NativeAnalyzerThread.STREAM_INPUT],
495                     ResultType.NEUTRAL,
496                     ResultUnit.NONE);
497         }
498 
addToJson(JSONObject jsonObject)499         void addToJson(JSONObject jsonObject) {
500             try {
501                 jsonObject.put(
502                         KEY_ROUTEINDEX,
503                         mRouteId);
504 
505                 jsonObject.put(
506                         KEY_LATENCY,
507                         mMeanLatencyMS);
508 
509                 jsonObject.put(
510                         KEY_CONFIDENCE,
511                         mMeanConfidence);
512 
513                 jsonObject.put(
514                         KEY_MEANABSDEVIATION,
515                         mMeanAbsoluteDeviation);
516 
517                 jsonObject.put(
518                         KEY_TEST_PERIPHERAL_NAME,
519                         mDeviceName);
520 
521                 jsonObject.put(
522                         KEY_TIMESTAMP_LATENCY,
523                         mMeanTimestampLatencyMS);
524 
525                 jsonObject.put(
526                         KEY_SAMPLE_RATE,
527                         mSampleRate);
528 
529                 jsonObject.put(
530                         KEY_HAS_24_BIT_HARDWARE_SUPPORT,
531                         mHas24BitHardwareSupport);
532 
533                 jsonObject.put(
534                         KEY_HARDWARE_FORMAT,
535                         mHardwareFormat);
536 
537                 jsonObject.put(
538                         KEY_OUTPUT_IS_LOW_LATENCY,
539                         mIsLowLatencyStream[NativeAnalyzerThread.STREAM_OUTPUT]);
540 
541                 jsonObject.put(
542                         KEY_INPUT_IS_LOW_LATENCY,
543                         mIsLowLatencyStream[NativeAnalyzerThread.STREAM_INPUT]);
544 
545                 jsonObject.put(
546                         KEY_OUTPUT_BURST_FRAMES,
547                         mBurstFrames[NativeAnalyzerThread.STREAM_OUTPUT]);
548 
549                 jsonObject.put(
550                         KEY_INPUT_BURST_FRAMES,
551                         mBurstFrames[NativeAnalyzerThread.STREAM_INPUT]);
552 
553                 jsonObject.put(
554                         KEY_OUTPUT_CAPACITY_FRAMES,
555                         mCapacityFrames[NativeAnalyzerThread.STREAM_OUTPUT]);
556 
557                 jsonObject.put(
558                         KEY_INPUT_CAPACITY_FRAMES,
559                         mCapacityFrames[NativeAnalyzerThread.STREAM_INPUT]);
560 
561                 jsonObject.put(
562                         KEY_OUTPUT_IS_MMAP_STREAM,
563                         mIsMMapStream[NativeAnalyzerThread.STREAM_OUTPUT]);
564 
565                 jsonObject.put(
566                         KEY_INPUT_IS_MMAP_STREAM,
567                         mIsMMapStream[NativeAnalyzerThread.STREAM_INPUT]);
568 
569             } catch (JSONException e) {
570                 Log.e(TAG, LOG_ERROR_STR, e);
571             }
572         }
573 
recordPerformanceClassTestResults()574         void recordPerformanceClassTestResults() {
575             PerformanceClassEvaluator pce = new PerformanceClassEvaluator(mTestName);
576             RoundTripAudioLatencyRequirement roundTripAudioLatencyRequirement =
577                     Requirements.addR5_6__H_1_2().to(pce);
578             TwentyFourBitAudioRequirement twentyFourBitAudioRequirement =
579                     Requirements.addR5_6__H_1_3().to(pce);
580 
581             roundTripAudioLatencyRequirement.setRoundTripAudioLatencyMs(mMeanLatencyMS);
582             twentyFourBitAudioRequirement.setTwentyFourBitAudioSupported(mHas24BitHardwareSupport);
583 
584             pce.submitAndVerify();
585         }
586     }
587 
588     @Override
onCreate(Bundle savedInstanceState)589     protected void onCreate(Bundle savedInstanceState) {
590         setContentView(R.layout.audio_loopback_latency_activity);
591 
592         super.onCreate(savedInstanceState);
593 
594         mContext = this;
595 
596         // MegaAudio Initialization
597         StreamBase.setup(this);
598 
599         setPassFailButtonClickListeners();
600         setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
601 
602         mRequireReportLogToPass = true;
603 
604         mClaimsOutput = AudioSystemFlags.claimsOutput(this);
605         mClaimsInput = AudioSystemFlags.claimsInput(this);
606         mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
607         mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this);
608         mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0;
609         mIsWatch = AudioSystemFlags.isWatch(this);
610         mIsTV = AudioSystemFlags.isTV(this);
611         mIsAutomobile = AudioSystemFlags.isAutomobile(this);
612         mIsHandheld = AudioSystemFlags.isHandheld(this);
613         mIsEmulator = Build.IS_EMULATOR;
614 
615         mUSBAudioSupport = AudioDeviceUtils.supportsUsbAudio(this);
616         mAnalogJackSupport = AudioDeviceUtils.supportsAnalogHeadset(this);
617 
618         // Setup test specifications
619         double mustLatency;
620 
621         // Speaker/Mic Path
622         mTestSpecs[TESTROUTE_DEVICE] =
623                 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT);
624         mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;    // Always
625 
626         // Analog Jack Path
627         mTestSpecs[TESTROUTE_ANALOG_JACK] =
628                 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED);
629 
630         // USB Path
631         mTestSpecs[TESTROUTE_USB] =
632                 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED);
633 
634         // Setup UI
635         mYesString = getString(R.string.audio_general_yes);
636         mNoString = getString(R.string.audio_general_no);
637         mPassString = getString(R.string.audio_general_teststatus_pass);
638         mFailString = getString(R.string.audio_general_teststatus_fail);
639         mNotTestedString = getString(R.string.audio_general_not_tested);
640         mRequiredString = getString(R.string.audio_general_required);
641         mNoHardwareString = getString(R.string.audio_general_nohardware);
642         mUnknownHardwareString = getString(R.string.audio_general_unknownhardware);
643 
644         // Utilities
645         mUtiltitiesHandler = new AudioLoopbackUtilitiesHandler(this);
646 
647         // Pro Audio
648         ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText(
649                 (mClaimsProAudio ? mYesString : mNoString));
650 
651         // Low Latency
652         ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText(
653                 (mClaimsLowLatency ? mYesString : mNoString));
654 
655         // Media Performance Class
656         ((TextView) findViewById(R.id.audio_loopback_mpc)).setText(
657                 (mClaimsMediaPerformance ? String.valueOf(Build.VERSION.MEDIA_PERFORMANCE_CLASS)
658                         : mNoString));
659 
660         // MMAP
661         ((TextView) findViewById(R.id.audio_loopback_mmap)).setText(
662                 (mSupportsMMAP ? mYesString : mNoString));
663         ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
664                 (mSupportsMMAPExclusive ? mYesString : mNoString));
665 
666         // Device Type
667         ((TextView) findViewById(R.id.audio_loopback_is_watch)).setText(
668                 (mIsWatch ? mYesString : mNoString));
669         ((TextView) findViewById(R.id.audio_loopback_is_TV)).setText(
670                 (mIsTV ? mYesString : mNoString));
671         ((TextView) findViewById(R.id.audio_loopback_is_automobile)).setText(
672                 (mIsAutomobile ? mYesString : mNoString));
673         ((TextView) findViewById(R.id.audio_loopback_is_handheld)).setText(
674                 (mIsHandheld ? mYesString : mNoString));
675         ((TextView) findViewById(R.id.audio_loopback_emulator)).setText(
676                 (mIsEmulator ? mYesString : mNoString));
677 
678         // Individual Test Results
679         mRouteStatus[TESTROUTE_DEVICE] =
680                 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info);
681         mRouteStatus[TESTROUTE_ANALOG_JACK] =
682                 (TextView) findViewById(R.id.audio_loopback_headsetpath_info);
683         mRouteStatus[TESTROUTE_USB] =
684                 (TextView) findViewById(R.id.audio_loopback_usbpath_info);
685 
686         mStartButtons[TESTROUTE_DEVICE] =
687                 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn);
688         mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener);
689 
690         mStartButtons[TESTROUTE_ANALOG_JACK] =
691                 (Button) findViewById(R.id.audio_loopback_headsetpath_btn);
692         mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener);
693 
694         mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn);
695         mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener);
696 
697         mTestInstructions = (TextView) findViewById(R.id.audio_loopback_instructions);
698 
699         mAudioManager = getSystemService(AudioManager.class);
700         scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
701 
702         // Utilties Buttons
703         if (mIsWatch) {
704             ((LinearLayout) findViewById(R.id.audio_loopback_utilities_layout))
705                     .setOrientation(LinearLayout.VERTICAL);
706         }
707 
708         mResultsWebView = findViewById(R.id.audio_loopback_results);
709 
710         connectLoopbackUI();
711 
712         if (mustRunTest()) {
713             getPassButton().setEnabled(false);
714         } else {
715             getPassButton().setEnabled(isReportLogOkToPass());
716         }
717 
718         mConnectListener = new ConnectListener();
719 
720         // WAV Capture
721         String folderName = getDataDir().getAbsolutePath() + "/WavFiles";
722         mFilesDir = new File(folderName);
723         if (!mFilesDir.exists()) {
724             mFilesDir.mkdir();
725         }
726 
727         mWavFileCapture = new WavFileCapture();
728 
729         showRouteStatus();
730         showTestInstructions();
731         handleTestCompletion(false);
732 
733         DisplayUtils.setKeepScreenOn(this, true);
734     }
735 
736     @Override
onStart()737     public void onStart() {
738         super.onStart();
739         mAudioManager.registerAudioDeviceCallback(mConnectListener, null);
740     }
741 
742     @Override
onStop()743     public void onStop() {
744         mAudioManager.unregisterAudioDeviceCallback(mConnectListener);
745         super.onStop();
746     }
747 
748     //
749     // UI State
750     //
showRouteStatus()751     private void showRouteStatus() {
752         // mRouteStatus[TESTROUTE_DEVICE];
753         // Nothing to do for this route.
754 
755         // mRouteStatus[TESTROUTE_ANALOG_JACK];
756         switch (mAnalogJackSupport) {
757             case AudioDeviceUtils.SUPPORTSDEVICE_NO:
758                 mRouteStatus[TESTROUTE_ANALOG_JACK].setText(
759                         getString(R.string.audio_loopback_noanalog));
760                 break;
761             case AudioDeviceUtils.SUPPORTSDEVICE_YES:
762                 mRouteStatus[TESTROUTE_ANALOG_JACK].setText(
763                         getString(R.string.audio_loopback_headsetpath_instructions));
764                 break;
765             case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
766                 mRouteStatus[TESTROUTE_ANALOG_JACK].setText(
767                         getString(R.string.audio_loopback_unknownanalog));
768                 break;
769         }
770 
771         // mRouteStatus[TESTROUTE_USB];
772         switch (mUSBAudioSupport) {
773             case AudioDeviceUtils.SUPPORTSDEVICE_NO:
774                 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_nousb));
775                 break;
776             case AudioDeviceUtils.SUPPORTSDEVICE_YES:
777                 mRouteStatus[TESTROUTE_USB].setText(
778                         getString(R.string.audio_loopback_usbpath_instructions));
779                 break;
780             case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
781                 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_unknownusb));
782                 break;
783         }
784     }
785 
showTestInstructions()786     private void showTestInstructions() {
787         if (mustRunTest()) {
788             mTestInstructions.setText(getString(R.string.audio_loopback_test_all_paths));
789         } else {
790             mTestInstructions.setText(getString(R.string.audio_loopback_test_not_required));
791         }
792     }
793 
enableStartButtons(boolean enable)794     private void enableStartButtons(boolean enable) {
795         if (enable) {
796             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
797                 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected);
798             }
799         } else {
800             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
801                 mStartButtons[routeId].setEnabled(false);
802             }
803         }
804         mUtiltitiesHandler.setEnabled(enable);
805     }
806 
connectLoopbackUI()807     private void connectLoopbackUI() {
808         mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text);
809         mTestStatusText.setVisibility(View.GONE);
810         mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar);
811         mProgressBar.setVisibility(View.GONE);
812         showWait(false);
813     }
814 
815     //
816     // Peripheral Connection Logic
817     //
clearDeviceIds()818     void clearDeviceIds() {
819         for (TestSpec testSpec : mTestSpecs) {
820             testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE;
821         }
822     }
823 
clearDeviceConnected()824     void clearDeviceConnected() {
825         for (TestSpec testSpec : mTestSpecs) {
826             testSpec.mRouteConnected = false;
827         }
828     }
829 
scanPeripheralList(AudioDeviceInfo[] devices)830     void scanPeripheralList(AudioDeviceInfo[] devices) {
831         clearDeviceIds();
832         clearDeviceConnected();
833 
834         mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
835         mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
836         for (AudioDeviceInfo devInfo : devices) {
837             switch (devInfo.getType()) {
838                 // TESTROUTE_DEVICE (i.e. Speaker & Mic)
839                 // This needs to be handled differently. The other devices can be assumed
840                 // to contain both input & output devices in the same type.
841                 // For built-in we need to see both TYPES to be sure to have both input & output.
842                 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
843                     mSpeakerDeviceId = devInfo.getId();
844                     break;
845                 case AudioDeviceInfo.TYPE_BUILTIN_MIC:
846                     mMicDeviceId = devInfo.getId();
847                     break;
848 
849                 // TESTROUTE_ANALOG_JACK
850                 case AudioDeviceInfo.TYPE_WIRED_HEADSET:
851                 case AudioDeviceInfo.TYPE_AUX_LINE:
852                     if (devInfo.isSink()) {
853                         mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId();
854                     } else if (devInfo.isSource()) {
855                         mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId();
856                     }
857                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true;
858                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true;
859                     mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName =
860                             devInfo.getProductName().toString();
861                     break;
862 
863                 // TESTROUTE_USB
864                 case AudioDeviceInfo.TYPE_USB_DEVICE:
865                 case AudioDeviceInfo.TYPE_USB_HEADSET:
866                     if (devInfo.isSink()) {
867                         mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId();
868                     } else if (devInfo.isSource()) {
869                         mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId();
870                     }
871                     mTestSpecs[TESTROUTE_USB].mRouteAvailable = true;
872                     mTestSpecs[TESTROUTE_USB].mRouteConnected = true;
873                     mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString();
874                     break;
875             }
876         }
877 
878         // do we have BOTH a Speaker and Mic?
879         if (hasInternalPath()) {
880             mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = mSpeakerDeviceId;
881             mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = mMicDeviceId;
882             mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;
883             mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true;
884             mTestSpecs[TESTROUTE_DEVICE].mDeviceName =
885                     getString(R.string.audio_loopback_test_internal_devices);
886         }
887 
888         enableStartButtons(mustRunTest());
889     }
890 
891     private class ConnectListener extends AudioDeviceCallback {
ConnectListener()892         ConnectListener() {}
893 
894         //
895         // AudioDevicesManager.OnDeviceConnectionListener
896         //
897         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)898         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
899             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
900             mUsbDeviceReport = AudioDeviceUtils.validateUsbDevice(mContext);
901         }
902 
903         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)904         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
905             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
906             mUsbDeviceReport = null;
907         }
908     }
909 
910     //
911     // show active progress bar
912     //
showWait(boolean show)913     protected void showWait(boolean show) {
914         mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
915     }
916 
917     //
918     // Common logging
919     //
920 
921     @Override
getTestId()922     public String getTestId() {
923         return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
924     }
925 
926     @Override
requiresReportLog()927     public boolean requiresReportLog() {
928         return true;
929     }
930 
931     @Override
getReportFileName()932     public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
933 
934     @Override
getReportSectionName()935     public final String getReportSectionName() {
936         return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
937     }
938 
939     // Global Test-Schema
940     private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
941     private static final String KEY_TEST_MMAP = "supports_mmap";
942     private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
943     private static final String KEY_LEVEL = "level";
944     private static final String KEY_PRODUCT_FIRST_API_LEVEL = "product_first_api_level";
945     private static final String KEY_BOARD_FIRST_API_LEVEL = "board_first_api_level";
946     private static final String KEY_OVERALL_PASS = "overall_pass";
947 
948     // Contains the results for all routes
949     private static final String KEY_PATHS = "paths";
950 
recordGlobalResults(CtsVerifierReportLog reportLog)951     private void recordGlobalResults(CtsVerifierReportLog reportLog) {
952         // Leave this in to be sure not to break the ReportLog injestion
953         reportLog.addValue(
954                 KEY_LEVEL,
955                 -1,
956                 ResultType.NEUTRAL,
957                 ResultUnit.NONE);
958 
959         reportLog.addValue(
960                 KEY_IS_PRO_AUDIO,
961                 mClaimsProAudio,
962                 ResultType.NEUTRAL,
963                 ResultUnit.NONE);
964 
965         reportLog.addValue(
966                 KEY_TEST_MMAP,
967                 mSupportsMMAP,
968                 ResultType.NEUTRAL,
969                 ResultUnit.NONE);
970 
971         reportLog.addValue(
972                 KEY_TEST_MMAPEXCLUSIVE,
973                 mSupportsMMAPExclusive,
974                 ResultType.NEUTRAL,
975                 ResultUnit.NONE);
976 
977         reportLog.addValue(
978                 Common.KEY_VERSION_CODE,
979                 Common.VERSION_CODE,
980                 ResultType.NEUTRAL,
981                 ResultUnit.NONE);
982 
983         reportLog.addValue(
984                 KEY_PRODUCT_FIRST_API_LEVEL,
985                 mFirstProductApiLevel,
986                 ResultType.NEUTRAL,
987                 ResultUnit.NONE);
988 
989         reportLog.addValue(
990                 KEY_BOARD_FIRST_API_LEVEL,
991                 mFirstBoardApiLevel,
992                 ResultType.NEUTRAL,
993                 ResultUnit.NONE);
994 
995         reportLog.addValue(
996                 KEY_OVERALL_PASS,
997                 mOverallPass,
998                 ResultType.NEUTRAL,
999                 ResultUnit.NONE);
1000     }
1001 
recordAllRoutes(CtsVerifierReportLog reportLog)1002     private void recordAllRoutes(CtsVerifierReportLog reportLog) {
1003         JSONArray jsonArray = new JSONArray();
1004         for (int route = 0; route < NUM_TEST_ROUTES; route++) {
1005             if (mTestSpecs[route].isMeasurementValid()) {
1006                 JSONObject jsonObject = new JSONObject();
1007                 mTestSpecs[route].addToJson(jsonObject);
1008                 jsonArray.put(jsonObject);
1009             }
1010         }
1011 
1012         if (jsonArray.length() > 0) {
1013             reportLog.addValues(KEY_PATHS, jsonArray);
1014         }
1015     }
1016 
1017     @Override
recordTestResults()1018     public void recordTestResults() {
1019         // Look for a valid route with the minimum latency.
1020         int bestRoute = -1;
1021         double minLatency = Double.MAX_VALUE;
1022         for (int route = 0; route < NUM_TEST_ROUTES; route++) {
1023             if (mTestSpecs[route].isMeasurementValid()) {
1024                 if (mTestSpecs[route].mMeanLatencyMS < minLatency) {
1025                     bestRoute = route;
1026                     minLatency = mTestSpecs[route].mMeanLatencyMS;
1027                 }
1028             }
1029         }
1030 
1031         if (bestRoute >= 0) {
1032             CtsVerifierReportLog reportLog = getReportLog();
1033             recordGlobalResults(reportLog);
1034             mTestSpecs[bestRoute].recordTestResults(reportLog);
1035             mTestSpecs[bestRoute].recordPerformanceClassTestResults();
1036             recordAllRoutes(reportLog);
1037             reportLog.submit();
1038         }
1039     }
1040 
startAudioTest(Handler messageHandler, int testRouteId)1041     private void startAudioTest(Handler messageHandler, int testRouteId) {
1042         enableStartButtons(false);
1043         mRouteStatus[testRouteId].setText(getString(R.string.audio_loopback_running));
1044 
1045         mTestRoute = testRouteId;
1046 
1047         mTestSpecs[mTestRoute].startTest();
1048 
1049         getPassButton().setEnabled(false);
1050 
1051         mTestPhase = 0;
1052 
1053         // Set mmap enabled as mmap supported to get best latency
1054         Globals.setMMapEnabled(Globals.isMMapSupported());
1055 
1056         mNativeAnalyzerThread = new NativeAnalyzerThread(this);
1057         if (mNativeAnalyzerThread != null) {
1058             mNativeAnalyzerThread.setMessageHandler(messageHandler);
1059             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
1060             mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
1061             startTestPhase();
1062         } else {
1063             Log.e(TAG, "Couldn't allocate native analyzer thread");
1064             mTestStatusText.setText(getString(R.string.audio_loopback_failure));
1065         }
1066     }
1067 
startTestPhase()1068     private void startTestPhase() {
1069         if (mNativeAnalyzerThread != null) {
1070             if (LOG) {
1071                 Log.d(TAG, "mTestRoute: " + mTestRoute
1072                         + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId
1073                         + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId);
1074             }
1075             mNativeAnalyzerThread.startTest(
1076                     mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId);
1077 
1078             // what is this for?
1079             try {
1080                 Thread.sleep(200);
1081             } catch (InterruptedException e) {
1082                 e.printStackTrace();
1083             }
1084         }
1085     }
1086 
handleTestPhaseCompletion()1087     private void handleTestPhaseCompletion() {
1088         if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
1089             double latency = mNativeAnalyzerThread.getLatencyMillis();
1090             double confidence = mNativeAnalyzerThread.getConfidence();
1091             double timestampLatency = mNativeAnalyzerThread.getTimestampLatencyMillis();
1092             TestSpec testSpec = mTestSpecs[mTestRoute];
1093             testSpec.recordPhase(mTestPhase, latency, confidence, timestampLatency);
1094 
1095             String result = String.format(getString(R.string.audio_loopback_testphaseresult),
1096                     mTestPhase, latency, confidence, timestampLatency);
1097 
1098             mTestStatusText.setText(result);
1099             try {
1100                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
1101                 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
1102             } catch (InterruptedException e) {
1103                 e.printStackTrace();
1104             }
1105 
1106 
1107             mTestPhase++;
1108             if (mTestPhase >= NUM_TEST_PHASES) {
1109                 handleTestCompletion(true);
1110             } else {
1111                 startTestPhase();
1112             }
1113         }
1114     }
1115 
mustRunTest()1116     private boolean mustRunTest() {
1117         // Special handling for emulators
1118         if (mIsEmulator) {
1119             return false;
1120         }
1121 
1122         return mIsHandheld && hasInternalPath();
1123     }
1124 
hasInternalPath()1125     boolean hasInternalPath() {
1126         return mSpeakerDeviceId != AudioDeviceInfo.TYPE_UNKNOWN
1127                 && mMicDeviceId != AudioDeviceInfo.TYPE_UNKNOWN;
1128     }
1129 
calcPass(LoopbackLatencyRequirements requirements)1130     private boolean calcPass(LoopbackLatencyRequirements requirements) {
1131         if (!mustRunTest()) {
1132             // just grant a pass on non-handheld devices
1133             return true;
1134         }
1135 
1136         boolean pass = true;
1137 
1138         // Check to see if the tests supported by the hardware have run
1139         // Analog Headset
1140         if (mAnalogJackSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES
1141                 && !mTestSpecs[TESTROUTE_ANALOG_JACK].mTestRun) {
1142             pass = false;
1143         }
1144 
1145         if (mUSBAudioSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES
1146                 && !mTestSpecs[TESTROUTE_USB].mTestRun) {
1147             pass = false;
1148         }
1149 
1150         // Check if the test values have passed
1151         // Even if the test is already a fail, this will generate the results string
1152         pass &= requirements.evaluate(mClaimsProAudio,
1153                 Build.VERSION.MEDIA_PERFORMANCE_CLASS,
1154                 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
1155                         ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0,
1156                 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
1157                         ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS :  0.0,
1158                 mTestSpecs[TESTROUTE_USB].isMeasurementValid()
1159                         ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0,
1160                 mTestSpecs[TESTROUTE_ANALOG_JACK].has24BitHardwareSupport(),
1161                 mTestSpecs[TESTROUTE_USB].has24BitHardwareSupport(),
1162                 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
1163                         ? mTestSpecs[TESTROUTE_DEVICE].mMeanTimestampLatencyMS : 0.0,
1164                 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
1165                         ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanTimestampLatencyMS :  0.0,
1166                 mTestSpecs[TESTROUTE_USB].isMeasurementValid()
1167                         ? mTestSpecs[TESTROUTE_USB].mMeanTimestampLatencyMS : 0.0);
1168 
1169         return pass;
1170     }
1171 
handleTestCompletion(boolean showResult)1172     private void handleTestCompletion(boolean showResult) {
1173         TestSpec testSpec = mTestSpecs[mTestRoute];
1174         testSpec.handleTestCompletion();
1175 
1176         // Make sure the test thread is finished. It should already be done.
1177         if (mNativeAnalyzerThread != null) {
1178             try {
1179                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
1180             } catch (InterruptedException e) {
1181                 e.printStackTrace();
1182             }
1183         }
1184 
1185         mRouteStatus[mTestRoute].setText(testSpec.getResultString());
1186 
1187         LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements();
1188         mOverallPass = calcPass(requirements);
1189 
1190         getPassButton().setEnabled(mOverallPass);
1191 
1192         showWait(false);
1193         enableStartButtons(true);
1194     }
1195 
1196     /**
1197      * handler for messages from audio thread
1198      */
1199     private Handler mMessageHandler = new Handler() {
1200         public void handleMessage(Message msg) {
1201             super.handleMessage(msg);
1202             Locale locale = Locale.getDefault();
1203 
1204             switch(msg.what) {
1205                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
1206                     Log.v(TAG,"got message native rec started!!");
1207                     showWait(true);
1208                     mTestStatusText.setText(String.format(locale,
1209                             getString(R.string.audio_loopback_phaserunning), (mTestPhase + 1)));
1210                     break;
1211                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
1212                     Log.v(TAG,"got message native rec can't start!!");
1213                     mTestStatusText.setText(getString(R.string.audio_loopback_erroropeningstream));
1214                     handleTestCompletion(true);
1215                     break;
1216                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
1217                     Log.v(TAG,"got message native rec can't start!!");
1218                     mTestStatusText.setText(getString(R.string.audio_loopback_errorwhilerecording));
1219                     handleTestCompletion(true);
1220                     break;
1221                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
1222                     mTestStatusText.setText(getString(R.string.audio_loopback_failedduetoerrors));
1223                     handleTestCompletion(true);
1224                     break;
1225                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
1226                     mTestStatusText.setText(String.format(locale,
1227                             getString(R.string.audio_loopback_phaseanalyzing), mTestPhase + 1));
1228                     break;
1229                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
1230                     handleTestPhaseCompletion();
1231                     break;
1232                 default:
1233                     break;
1234             }
1235         }
1236     };
1237 
1238     private class OnBtnClickListener implements OnClickListener {
1239         @Override
onClick(View v)1240         public void onClick(View v) {
1241             int id = v.getId();
1242             if (id == R.id.audio_loopback_speakermicpath_btn) {
1243                 startAudioTest(mMessageHandler, TESTROUTE_DEVICE);
1244             } else if (id == R.id.audio_loopback_headsetpath_btn) {
1245                 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK);
1246             }  else if (id == R.id.audio_loopback_usbpath_btn) {
1247                 startAudioTest(mMessageHandler, TESTROUTE_USB);
1248             }
1249         }
1250     }
1251 
1252     class LoopbackLatencyRequirements {
1253         public static final int MPC_NONE = 0;
1254         public static final int MPC_R = Build.VERSION_CODES.R;
1255         public static final int MPC_S = Build.VERSION_CODES.S;
1256         public static final int MPC_T = Build.VERSION_CODES.TIRAMISU;
1257 
1258         String mResultsString = new String();
1259 
getResultsString()1260         String getResultsString() {
1261             return mResultsString;
1262         }
1263 
checkLatency(double measured, double limit)1264         private boolean checkLatency(double measured, double limit) {
1265             return measured == LATENCY_NOT_MEASURED || measured <= limit;
1266         }
1267 
checkTimestampLatencyAccuracy(double measuredLatency, double timestampLatency)1268         private boolean checkTimestampLatencyAccuracy(double measuredLatency,
1269                 double timestampLatency) {
1270             return (timestampLatency < 0.0) || (measuredLatency == LATENCY_NOT_MEASURED)
1271                     || (Math.abs(measuredLatency - timestampLatency) <= TIMESTAMP_ACCURACY_MS);
1272         }
1273 
1274         /**
1275          * Calculate pass/not pass and generate on-screen messages.
1276          * @return true if the test is in a pass state.
1277          */
evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency, boolean analog24BitHardwareSupport, boolean usb24BitHardwareSupport, double deviceTimestampLatency, double analogTimestampLatency, double usbTimestampLatency)1278         public boolean evaluate(boolean proAudio,
1279                                        int mediaPerformanceClass,
1280                                        double deviceLatency,
1281                                        double analogLatency,
1282                                        double usbLatency,
1283                                        boolean analog24BitHardwareSupport,
1284                                        boolean usb24BitHardwareSupport,
1285                                        double deviceTimestampLatency,
1286                                        double analogTimestampLatency,
1287                                        double usbTimestampLatency) {
1288 
1289             if (LOG) {
1290                 Log.d(TAG, "evaluate()");
1291             }
1292 
1293             /*
1294              * Calculate PASS/FAIL
1295              */
1296 
1297            // Required to test the Mic/Speaker path
1298             boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED;
1299             if (LOG) {
1300                 Log.d(TAG, "  internalPathRun:" + internalPathRun);
1301             }
1302 
1303             // *ALL* devices must be under the basic limit.
1304             boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC)
1305                     && checkLatency(analogLatency, LATENCY_BASIC)
1306                     && checkLatency(usbLatency, LATENCY_BASIC);
1307             if (LOG) {
1308                 Log.d(TAG, "  basicPass:" + basicPass);
1309             }
1310 
1311             // For Media Performance Class T the RT latency must be <= 80 msec on one path.
1312             boolean mpcAtLeastOnePass;
1313             if (mClaimsMediaPerformance) {
1314                 mpcAtLeastOnePass =
1315                     (mediaPerformanceClass < MPC_T)
1316                             || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE)
1317                             || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE)
1318                             || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE);
1319             } else {
1320                 mpcAtLeastOnePass = true;
1321             }
1322             if (LOG) {
1323                 Log.d(TAG, "  mpcAtLeastOnePass:" + mpcAtLeastOnePass);
1324             }
1325 
1326             // For ProAudio, the RT latency must be <= 25 msec on one path.
1327             boolean proAudioAtLeastOnePass = !proAudio
1328                     || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
1329                     || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
1330                     || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE);
1331             if (LOG) {
1332                 Log.d(TAG, "  proAudioAtLeastOnePass:" + proAudioAtLeastOnePass);
1333             }
1334 
1335             // For ProAudio, analog and USB have specific limits
1336             boolean proAudioLimitsPass = !proAudio;
1337             if (proAudio) {
1338                 if (analogLatency > 0.0) {
1339                     proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG;
1340                 } else if (usbLatency > 0.0) {
1341                     // USB audio must be supported if 3.5mm jack not supported
1342                     proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB;
1343                 }
1344             }
1345 
1346             // For Media Performance Class T, usb and analog should support >=24 bit audio.
1347             boolean has24BitHardwareSupportPass = (mediaPerformanceClass < MPC_T)
1348                     || (analog24BitHardwareSupport && usb24BitHardwareSupport);
1349             if (LOG) {
1350                 Log.d(TAG, "  has24BitHardwareSupportPass:" + has24BitHardwareSupportPass);
1351             }
1352 
1353             // Timestamp latencies must be accurate enough.
1354             boolean timestampPass =
1355                     checkTimestampLatencyAccuracy(deviceLatency, deviceTimestampLatency)
1356                     && checkTimestampLatencyAccuracy(analogLatency, analogTimestampLatency)
1357                     && checkTimestampLatencyAccuracy(usbLatency, usbTimestampLatency);
1358             if (LOG) {
1359                 Log.d(TAG, "  timestampPass:" + timestampPass);
1360             }
1361 
1362             boolean pass =
1363                     internalPathRun
1364                     && basicPass
1365                     && mpcAtLeastOnePass
1366                     && proAudioAtLeastOnePass
1367                     && proAudioLimitsPass
1368                     && has24BitHardwareSupportPass;
1369             if (LOG) {
1370                 Log.d(TAG, "  pass:" + pass);
1371             }
1372 
1373             /*
1374              * Display Results
1375              */
1376             buildResultsPanel(proAudio, mediaPerformanceClass);
1377 
1378             return pass;
1379         }
1380 
reportCapture(TextFormatter textFormatter, int pathId)1381         private void reportCapture(TextFormatter textFormatter, int pathId) {
1382             if (mTestSpecs[pathId].mCaptureCode == WavFileCapture.CAPTURE_SUCCESS) {
1383                 textFormatter.appendBreak()
1384                         .appendText(getString(R.string.audio_loopback_wavcapturefile) + " "
1385                                 + mTestSpecs[pathId].mWavCaptureFileName);
1386             } else {
1387                 if (mTestSpecs[pathId].mWavCaptureFileName == null) {
1388                     textFormatter.appendBreak()
1389                             .appendText(getString(R.string.audio_loopback_nocapture));
1390                 } else {
1391                     textFormatter.appendBreak()
1392                             .openBold()
1393                             .appendText(getString(R.string.audio_loopback_nocapture)
1394                                 + ": " + mTestSpecs[pathId].mWavCaptureFileName)
1395                             .closeBold();
1396                 }
1397             }
1398         }
1399 
buildResultsPanel(boolean proAudio, int mediaPerformanceClass)1400         private void buildResultsPanel(boolean proAudio, int mediaPerformanceClass) {
1401             // We will want to think about non-WebView devices
1402             TextFormatter textFormatter = new HtmlFormatter();
1403             textFormatter.openDocument();
1404 
1405             Locale locale = Locale.getDefault();
1406 
1407             /*
1408              * ReportLog Warning
1409              */
1410             if (!isReportLogOkToPass()) {
1411                 textFormatter.openParagraph()
1412                         .appendText(getString(R.string.ctsv_general_cantwritereportlog))
1413                         .closeParagraph();
1414             }
1415 
1416             /*
1417              * Audio Performance Level Criteria
1418              */
1419             textFormatter.appendText(getString(R.string.ctsv_loopback_criteria))
1420                     .openBold();
1421             if (proAudio) {
1422                 textFormatter.appendText(getString(R.string.audio_general_proaudio));
1423             } else if (mediaPerformanceClass != MPC_NONE) {
1424                 textFormatter.appendText(getString(R.string.audio_general_mpc) + " "
1425                         + mediaPerformanceClass);
1426             } else {
1427                 textFormatter.appendText(getString(R.string.audio_general_basicaudio));
1428             }
1429             textFormatter.closeBold()
1430                     .appendBreak();
1431 
1432             // Basic Pass
1433             textFormatter.appendText(getString(R.string.ctsv_loopback_forbasiclatency))
1434                     .appendBreak();
1435             textFormatter.appendText(getString(R.string.ctsv_loopback_atleastoneroute)
1436                             + LATENCY_BASIC + getString(R.string.ctsv_general_mssuffix))
1437                     .appendBreak();
1438 
1439             // Media Performance Class
1440             if (mClaimsMediaPerformance) {
1441                 textFormatter.appendText(
1442                         getString(R.string.ctsv_loopback_formpclevel) + " " + mediaPerformanceClass)
1443                         .appendBreak()
1444                         .appendText(getString(R.string.ctsv_loopback_atleastoneroute)
1445                                 + LATENCY_MPC_AT_LEAST_ONE
1446                                 + getString(R.string.ctsv_general_mssuffix))
1447                         .appendBreak();
1448             }
1449 
1450             // Pro Audio
1451             if (proAudio) {
1452                 textFormatter.appendText(getString(R.string.ctsv_loopback_forproaudio))
1453                         .appendBreak()
1454                         .appendText(getString(R.string.ctsv_loopback_analogheadsetspec)
1455                                 + LATENCY_PRO_AUDIO_ANALOG
1456                                 + getString(R.string.ctsv_general_mssuffix))
1457                         .appendBreak()
1458                         .appendText(getString(R.string.ctsv_loopback_usbspec)
1459                                 + LATENCY_PRO_AUDIO_USB
1460                                 + getString(R.string.ctsv_general_mssuffix))
1461                         .appendBreak();
1462             }
1463 
1464             textFormatter.appendBreak()
1465                     .appendText(getString(R.string.audio_loopback_wavcapturefolder))
1466                     .appendBreak()
1467                     .appendText(mFilesDir.getPath())
1468                     .appendBreak();
1469 
1470             /*
1471              * Speaker/Mic route
1472              */
1473             double speakermicLatency = mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS;
1474             textFormatter.openParagraph()
1475                     .openBold()
1476                     .appendText(getString(R.string.audio_loopback_speakermic))
1477                     .closeBold()
1478                     .appendBreak();
1479             if (speakermicLatency == LATENCY_NOT_MEASURED) {
1480                 textFormatter.openItalic()
1481                         .appendText(getString(R.string.ctsv_loopback_testspeakermic))
1482                         .closeItalic();
1483             } else {
1484                 textFormatter.appendText(String.format(locale, "%.2f ms ", speakermicLatency));
1485                 if (speakermicLatency <= LATENCY_PRO_AUDIO_AT_LEAST_ONE) {
1486                     textFormatter.appendText(" - "
1487                             + getString(R.string.ctsv_loopback_meetsproaudio)
1488                             + String.format(locale, " (%.2f ms)", LATENCY_PRO_AUDIO_AT_LEAST_ONE));
1489                 } else if (mClaimsMediaPerformance
1490                         && speakermicLatency <= LATENCY_MPC_AT_LEAST_ONE) {
1491                     textFormatter.appendText(" - "
1492                             + getString(R.string.ctsv_loopback_meetsmpclevel) + " "
1493                             + Build.VERSION.MEDIA_PERFORMANCE_CLASS + " "
1494                             + getString(R.string.ctsv_general_specification)
1495                             + String.format(locale, " (%.2f ms)", LATENCY_MPC_AT_LEAST_ONE));
1496                 } else if (speakermicLatency <= LATENCY_BASIC) {
1497                     textFormatter.appendText(" - "
1498                             + getString(R.string.ctsv_loopback_meetsbasicaudio)
1499                             + String.format(locale, " (%.2f ms)", LATENCY_BASIC));
1500                 } else {
1501                     textFormatter.appendText(getString(R.string.ctsv_general_failsuffix));
1502                 }
1503             }
1504 
1505             // Capture Info
1506             reportCapture(textFormatter, TESTROUTE_DEVICE);
1507             textFormatter.closeParagraph();
1508 
1509             /*
1510              * Analog Headset Route
1511              */
1512             double analogLatency = mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS;
1513             textFormatter.openParagraph()
1514                     .openBold()
1515                     .appendText(getString(R.string.audio_loopback_headset))
1516                     .closeBold()
1517                     .appendBreak();
1518             if (analogLatency != LATENCY_NOT_MEASURED) {
1519                 // we have a legit measurement
1520                 textFormatter.appendText(String.format(locale, "%.2f ms ", analogLatency));
1521                 if (analogLatency <= LATENCY_PRO_AUDIO_ANALOG) {
1522                     textFormatter.appendText(" - "
1523                             + getString(R.string.ctsv_loopback_meetsproaudio)
1524                             + String.format(locale, " (%.2f ms)", LATENCY_PRO_AUDIO_ANALOG));
1525                 } else if (mClaimsMediaPerformance && analogLatency <= LATENCY_MPC_AT_LEAST_ONE) {
1526                     textFormatter.appendText(" - "
1527                             + getString(R.string.ctsv_loopback_meetsmpclevel) + " "
1528                             + Build.VERSION.MEDIA_PERFORMANCE_CLASS + " "
1529                             + getString(R.string.ctsv_general_specification)
1530                             + String.format(locale, " (%.2f ms)", LATENCY_MPC_AT_LEAST_ONE));
1531                 } else if (analogLatency <= LATENCY_BASIC) {
1532                     textFormatter.appendText(" - "
1533                             + getString(R.string.ctsv_loopback_meetsbasicaudio)
1534                             + String.format(locale, " (%.2f ms)", LATENCY_BASIC));
1535                 } else {
1536                     textFormatter.appendText(getString(R.string.ctsv_general_failsuffix));
1537                 }
1538             } else {
1539                 // Not measured
1540                 textFormatter.openItalic();
1541                 switch (mAnalogJackSupport) {
1542                     case AudioDeviceUtils.SUPPORTSDEVICE_YES:
1543                         textFormatter.appendText(getString(R.string.ctsv_loopback_testanalogjack));
1544                         break;
1545 
1546                     case AudioDeviceUtils.SUPPORTSDEVICE_NO:
1547                         textFormatter.appendText(
1548                                 getString(R.string.ctsv_loopback_noanalogjacksupport));
1549                         break;
1550 
1551                     case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
1552                     default:
1553                         textFormatter.appendText(
1554                                 getString(R.string.ctsv_loopback_unknownanalogsupport));
1555                         break;
1556                 }
1557                 textFormatter.closeItalic();
1558             }
1559 
1560             // Capture Info
1561             reportCapture(textFormatter, TESTROUTE_ANALOG_JACK);
1562             textFormatter.closeParagraph();
1563 
1564             /*
1565              * USB Device
1566              */
1567             double usbLatency = mTestSpecs[TESTROUTE_USB].mMeanLatencyMS;
1568             textFormatter.openParagraph()
1569                     .openBold()
1570                     .appendText(getString(R.string.audio_loopback_usb))
1571                     .closeBold()
1572                     .appendBreak();
1573             if (usbLatency != LATENCY_NOT_MEASURED) {
1574                 textFormatter.appendText(String.format(locale, "%.2f ms ", usbLatency));
1575                 if (usbLatency <= LATENCY_PRO_AUDIO_USB) {
1576                     textFormatter.appendText(" - "
1577                             + getString(R.string.ctsv_loopback_meetsproaudio)
1578                             + String.format(locale, " (%.2f ms)", LATENCY_PRO_AUDIO_USB));
1579                 } else if (mClaimsMediaPerformance && usbLatency <= LATENCY_MPC_AT_LEAST_ONE) {
1580                     textFormatter.appendText(" - "
1581                             + getString(R.string.ctsv_loopback_meetsmpclevel) + " "
1582                             + Build.VERSION.MEDIA_PERFORMANCE_CLASS + " "
1583                             + getString(R.string.ctsv_general_specification)
1584                             + String.format(locale, " (%.2f ms", LATENCY_MPC_AT_LEAST_ONE));
1585                 } else if (usbLatency <= LATENCY_BASIC) {
1586                     textFormatter.appendText(" - "
1587                             + getString(R.string.ctsv_loopback_meetsbasicaudio)
1588                             + String.format(locale, " (%.2f ms)", LATENCY_BASIC));
1589                 } else {
1590                     textFormatter.appendText(getString(R.string.ctsv_general_failsuffix));
1591                 }
1592             } else {
1593                 // Not measured
1594                 textFormatter.openItalic();
1595                 switch (mUSBAudioSupport) {
1596                     case AudioDeviceUtils.SUPPORTSDEVICE_YES:
1597                         textFormatter.appendText(getString(R.string.ctsv_loopback_testusb));
1598                         break;
1599 
1600                     case AudioDeviceUtils.SUPPORTSDEVICE_NO:
1601                         textFormatter.appendText(getString(R.string.ctsv_loopback_nousbsupport));
1602                         break;
1603 
1604                     case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
1605                     default:
1606                         textFormatter.appendText(
1607                                 getString(R.string.ctsv_loopback_unknownusbsupport));
1608                         break;
1609                 }
1610                 textFormatter.closeItalic();
1611             }
1612 
1613             // Capture Info
1614             reportCapture(textFormatter, TESTROUTE_USB);
1615             textFormatter.closeParagraph();
1616 
1617             textFormatter.openParagraph();
1618 
1619             boolean analogRequired = mAnalogJackSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES;
1620             boolean usbRequired = mUSBAudioSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES;
1621 
1622             // First, do we have enough data to present a result.
1623             // Have all available paths been tested.
1624             boolean testSufficient =
1625                     speakermicLatency != LATENCY_NOT_MEASURED
1626                     && (analogLatency != LATENCY_NOT_MEASURED || !analogRequired)
1627                     && (usbLatency != LATENCY_NOT_MEASURED || !usbRequired);
1628 
1629             if (!testSufficient) {
1630                 textFormatter.openItalic()
1631                         .appendText(getString(R.string.ctsv_loopback_testallroutes))
1632                         .closeItalic();
1633             } else {
1634                 // All devices must be under the basic limit.
1635                 boolean basicPass = checkLatency(speakermicLatency, LATENCY_BASIC)
1636                         && checkLatency(analogLatency, LATENCY_BASIC)
1637                         && checkLatency(usbLatency, LATENCY_BASIC);
1638 
1639                 textFormatter.appendText(getString(R.string.audio_loopback_basiclatency) + " ")
1640                         .openBold()
1641                         .appendText(getString(basicPass
1642                                 ? R.string.ctsv_general_pass : R.string.ctsv_general_fail))
1643                         .closeBold()
1644                         .appendBreak();
1645 
1646                 // MPC
1647                 boolean mpcAtLeastOnePass =
1648                         (mediaPerformanceClass < MPC_T)
1649                         || checkLatency(speakermicLatency, LATENCY_MPC_AT_LEAST_ONE)
1650                         || (analogRequired && checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE))
1651                         || (usbRequired && checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE));
1652 
1653                 textFormatter.appendText(getString(R.string.audio_loopback_mpclatency) + " "
1654                                 + mediaPerformanceClass + ": ")
1655                         .openBold();
1656                 if (mClaimsMediaPerformance) {
1657                     textFormatter.appendText(getString(mpcAtLeastOnePass
1658                                     ? R.string.ctsv_general_pass : R.string.ctsv_general_fail));
1659                 } else {
1660                     textFormatter.appendText(getString(R.string.audio_loopback_nopmpcsupport));
1661                 }
1662                 textFormatter.closeBold()
1663                         .appendBreak();
1664 
1665                 // Pro Audio
1666                 // For ProAudio, the RT latency must be <= 25 msec on one path.
1667                 boolean proAudioAtLeastOnePass =
1668                         checkLatency(speakermicLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
1669                         || (analogRequired
1670                                 && checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE))
1671                         || (usbRequired
1672                                 && checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE));
1673 
1674                 textFormatter
1675                         .appendText(getString(R.string.audio_loopback_proaudiolatency) + ": ")
1676                         .openBold();
1677                 if (mClaimsProAudio) {
1678                     textFormatter.appendText(
1679                             getString(
1680                                     proAudioAtLeastOnePass
1681                                             ? R.string.ctsv_general_pass
1682                                             : R.string.ctsv_general_fail));
1683                 } else {
1684                     textFormatter.appendText(getString(R.string.audio_loopback_noproaudiosupport));
1685                 }
1686                 textFormatter.closeBold().appendBreak();
1687             }
1688 
1689             textFormatter.closeParagraph()
1690                     .closeDocument();
1691 
1692             textFormatter.put(mResultsWebView);
1693             mResultsWebView.setVisibility(View.VISIBLE);
1694         }
1695     }
1696 }
1697