xref: /aosp_15_r20/cts/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioDataPathsBaseActivity.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2022 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.Intent;
23 import android.graphics.Color;
24 import android.media.AudioDeviceCallback;
25 import android.media.AudioDeviceInfo;
26 import android.media.AudioManager;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.os.Environment;
30 import android.util.Log;
31 import android.view.View;
32 import android.view.ViewGroup.LayoutParams;
33 import android.webkit.WebView;
34 import android.widget.LinearLayout;
35 import android.widget.TextView;
36 
37 import com.android.compatibility.common.util.ResultType;
38 import com.android.compatibility.common.util.ResultUnit;
39 import com.android.cts.verifier.CtsVerifierReportLog;
40 import com.android.cts.verifier.PassFailButtons;
41 import com.android.cts.verifier.R;
42 import com.android.cts.verifier.audio.analyzers.BaseSineAnalyzer;
43 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils;
44 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
45 import com.android.cts.verifier.audio.audiolib.DisplayUtils;
46 import com.android.cts.verifier.audio.audiolib.WaveFileWriter;
47 import com.android.cts.verifier.audio.audiolib.WaveScopeView;
48 import com.android.cts.verifier.libs.ui.HtmlFormatter;
49 import com.android.cts.verifier.libs.ui.PlainTextFormatter;
50 import com.android.cts.verifier.libs.ui.TextFormatter;
51 
52 import org.hyphonate.megaaudio.common.BuilderBase;
53 import org.hyphonate.megaaudio.common.Globals;
54 import org.hyphonate.megaaudio.common.StreamBase;
55 import org.hyphonate.megaaudio.duplex.DuplexAudioManager;
56 import org.hyphonate.megaaudio.player.AudioSource;
57 import org.hyphonate.megaaudio.player.AudioSourceProvider;
58 import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
59 import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
60 
61 import java.io.File;
62 import java.io.IOException;
63 import java.text.DateFormat;
64 import java.util.ArrayList;
65 import java.util.Calendar;
66 import java.util.Date;
67 import java.util.Locale;
68 import java.util.Timer;
69 import java.util.TimerTask;
70 
71 /**
72  * CtsVerifier test for audio data paths.
73  */
74 public abstract class AudioDataPathsBaseActivity
75         extends AudioMultiApiActivity
76         implements View.OnClickListener, AppCallback {
77     private static final String TAG = "AudioDataPathsActivity";
78 
79     // ReportLog Schema
80     private static final String SECTION_AUDIO_DATAPATHS = "audio_datapaths";
81 
82     protected boolean mHasMic;
83     protected boolean mHasSpeaker;
84 
85     // UI
86     protected View mStartButton;
87     protected View mCancelButton;
88     protected View mClearResultsButton;
89     protected View mShowResultsButton;
90     protected View mShareResultsButton;
91 
92     protected AudioLoopbackUtilitiesHandler mUtiltitiesHandler;
93 
94     private TextView mRoutesTx;
95     private View mResultsView;
96 
97     private WaveScopeView mWaveView = null;
98 
99     private TextFormatter mTextFormatter;
100 
101     // Test Manager
102     protected TestManager mTestManager = new TestManager();
103     private boolean mTestHasBeenRun;
104     private boolean mTestCanceledByUser;
105 
106     // Audio I/O
107     private AudioManager mAudioManager;
108 
109     AudioDeviceConnectionCallback mConnectListener;
110 
111     private boolean mSupportsMMAP;
112     private boolean mSupportsMMAPExclusive;
113 
114     protected boolean mIsHandheld;
115 
116     // Analysis
117     private BaseSineAnalyzer mAnalyzer = new BaseSineAnalyzer();
118 
119     private DuplexAudioManager mDuplexAudioManager;
120 
121     protected AppCallback mAnalysisCallbackHandler;
122     private File mRecordingDir;
123 
124     @Override
onCreate(Bundle savedInstanceState)125     protected void onCreate(Bundle savedInstanceState) {
126         super.onCreate(savedInstanceState);
127 
128         // MegaAudio Initialization
129         StreamBase.setup(this);
130 
131         //
132         // Header Fields
133         //
134         // When it is first created, isMMapEnabled will always return false due to b/326989822
135         // Reenable this code when the fix lands in extern/oboe.
136         mSupportsMMAP = Globals.isMMapSupported() /*&& Globals.isMMapEnabled()*/;
137         mSupportsMMAPExclusive = Globals.isMMapExclusiveSupported() /*&& Globals.isMMapEnabled()*/;
138 
139         mHasMic = AudioSystemFlags.claimsInput(this);
140         mHasSpeaker = AudioSystemFlags.claimsOutput(this);
141 
142         mIsHandheld = AudioSystemFlags.isHandheld(this);
143 
144         String yesString = getResources().getString(R.string.audio_general_yes);
145         String noString = getResources().getString(R.string.audio_general_no);
146         ((TextView) findViewById(R.id.audio_datapaths_mic))
147                 .setText(mHasMic ? yesString : noString);
148         ((TextView) findViewById(R.id.audio_datapaths_speaker))
149                 .setText(mHasSpeaker ? yesString : noString);
150         ((TextView) findViewById(R.id.audio_datapaths_MMAP))
151                 .setText(mSupportsMMAP ? yesString : noString);
152         ((TextView) findViewById(R.id.audio_datapaths_MMAP_exclusive))
153                 .setText(mSupportsMMAPExclusive ? yesString : noString);
154 
155         // Utilities
156         mUtiltitiesHandler = new AudioLoopbackUtilitiesHandler(this);
157 
158         mStartButton = findViewById(R.id.audio_datapaths_start);
159         mStartButton.setOnClickListener(this);
160         mCancelButton = findViewById(R.id.audio_datapaths_cancel);
161         mCancelButton.setOnClickListener(this);
162         mCancelButton.setEnabled(false);
163         (mClearResultsButton =
164                 findViewById(R.id.audio_datapaths_clearresults)).setOnClickListener(this);
165         (mShowResultsButton =
166                 findViewById(R.id.audio_datapaths_showresults)).setOnClickListener(this);
167         (mShareResultsButton =
168                 findViewById(R.id.audio_datapaths_shareresults)).setOnClickListener(this);
169         mRoutesTx = (TextView) findViewById(R.id.audio_datapaths_routes);
170 
171         LinearLayout resultsLayout = findViewById(R.id.audio_datapaths_results);
172         if (AudioSystemFlags.supportsWebView(this)) {
173             mTextFormatter = new HtmlFormatter();
174             mResultsView = new WebView(this);
175         } else {
176             // No WebView
177             mTextFormatter = new PlainTextFormatter();
178             mResultsView = new TextView(this);
179         }
180         resultsLayout.addView(mResultsView,
181                 new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
182 
183         boolean isWatch = AudioSystemFlags.isWatch(this);
184         if (isWatch) {
185             // Device Attributes Header
186             ((LinearLayout) findViewById(R.id.audio_datapaths_header))
187                     .setOrientation(LinearLayout.VERTICAL);
188             // Utilities UI
189             ((LinearLayout) findViewById(R.id.audio_loopback_utilities_layout))
190                     .setOrientation(LinearLayout.VERTICAL);
191         }
192 
193         mWaveView = findViewById(R.id.uap_recordWaveView);
194         mWaveView.setBackgroundColor(Color.DKGRAY);
195         mWaveView.setTraceColor(Color.WHITE);
196 
197         setPassFailButtonClickListeners();
198 
199         mAudioManager = getSystemService(AudioManager.class);
200 
201         mAnalysisCallbackHandler = this;
202 
203         mTestManager.initializeTests();
204 
205         mConnectListener = new AudioDeviceConnectionCallback();
206 
207         DisplayUtils.setKeepScreenOn(this, true);
208 
209         getPassButton().setEnabled(!mIsHandheld || !hasPeripheralSupport());
210         if (!mIsHandheld) {
211             displayNonHandheldMessage();
212         }
213 
214         // Write to a directory that can be read on production builds using 'adb pull'.
215         // This works because we have WRITE_EXTERNAL_STORAGE permission.
216         mRecordingDir = new File(Environment.getExternalStorageDirectory(), "verifierWaves");
217         if (!mRecordingDir.exists()) {
218             mRecordingDir.mkdir();
219         }
220     }
221 
222     @Override
onStart()223     public void onStart() {
224         super.onStart();
225         mAudioManager.registerAudioDeviceCallback(mConnectListener, null);
226     }
227 
228     @Override
onStop()229     public void onStop() {
230         stopTest();
231         mAudioManager.unregisterAudioDeviceCallback(mConnectListener);
232         super.onStop();
233     }
234 
235     //
236     // UI Helpers
237     //
getTestCategory()238     protected abstract String getTestCategory();
239 
enableTestButtons(boolean enabled)240     protected void enableTestButtons(boolean enabled) {
241         mStartButton.setEnabled(enabled);
242         mClearResultsButton.setEnabled(enabled);
243         mShareResultsButton.setEnabled(enabled);
244     }
245 
enableTestButtons(boolean startEnabled, boolean stopEnabled)246     void enableTestButtons(boolean startEnabled, boolean stopEnabled) {
247         mStartButton.setEnabled(startEnabled);
248         mClearResultsButton.setEnabled(startEnabled);
249         mShareResultsButton.setEnabled(startEnabled);
250         mCancelButton.setEnabled(stopEnabled);
251     }
252 
showDeviceView()253     private void showDeviceView() {
254         mRoutesTx.setVisibility(View.VISIBLE);
255         mWaveView.setVisibility(View.VISIBLE);
256 
257         mResultsView.setVisibility(View.GONE);
258     }
259 
showResultsView()260     private void showResultsView() {
261         mRoutesTx.setVisibility(View.GONE);
262         mWaveView.setVisibility(View.GONE);
263 
264         mResultsView.setVisibility(View.VISIBLE);
265         mResultsView.invalidate();
266     }
267 
268     class TestModule implements Cloneable {
269         //
270         // Analysis Type
271         //
272         public static final int TYPE_SIGNAL_PRESENCE    = 0;
273         public static final int TYPE_SIGNAL_ABSENCE     = 1;
274         private int mAnalysisType = TYPE_SIGNAL_PRESENCE;
275 
276         //
277         // Datapath specifications
278         //
279         // Playback Specification
280         final int mOutDeviceType; // TYPE_BUILTIN_SPEAKER for example
281         final int mOutSampleRate;
282         final int mOutChannelCount;
283         int mOutPerformanceMode;
284         //TODO - Add usage and content types to output stream
285 
286         // Device for capturing the (played) signal
287         final int mInDeviceType;  // TYPE_BUILTIN_MIC for example
288         final int mInSampleRate;
289         final int mInChannelCount;
290         int mInPerformanceMode;
291 
292         int mAnalysisChannel = 0;
293         int mInputPreset;
294         int mModuleIndex;
295 
296         AudioDeviceInfo mOutDeviceInfo;
297         AudioDeviceInfo mInDeviceInfo;
298 
299         static final int TRANSFER_LEGACY = 0;
300         static final int TRANSFER_MMAP_SHARED = 1;
301         static final int TRANSFER_MMAP_EXCLUSIVE = 2;
302         int mTransferType = TRANSFER_LEGACY;
303 
304         public AudioSourceProvider mSourceProvider;
305         public AudioSinkProvider mSinkProvider;
306 
307         private String mSectionTitle = null;
308         private String mDescription = "";
309 
310         private String mSavedFileMessage; // OK if null
311 
312         private static final String PLAYER_FAILED_TO_GET_STRING = "Player failed to get ";
313         private static final String RECORDER_FAILED_TO_GET_STRING = "Recorder failed to get ";
314 
315         int[] mTestStateCode;
316         TestStateData[] mTestStateData;
317 
318         TestResults[] mTestResults;
319 
320         // Pass/Fail criteria (with defaults)
321         static final double MIN_SIGNAL_PASS_MAGNITUDE = 0.01;
322         static final double MAX_SIGNAL_PASS_JITTER = 0.1;
323         static final double MAX_XTALK_PASS_MAGNITUDE = 0.02;
324 
325         //
326         // A set of classes to store information specific to the
327         // different failure modes
328         //
329         abstract  class TestStateData {
330             final TestModule  mTestModule;
331 
TestStateData(TestModule testModule)332             TestStateData(TestModule testModule) {
333                 mTestModule = testModule;
334             }
335 
buildErrorString(TestModule testModule)336             abstract String buildErrorString(TestModule testModule);
337         }
338 
339         //
340         // Stores information about sharing mode failures
341         //
342         class BadSharingTestState extends TestStateData {
343             final boolean mPlayerFailed;
344             final boolean mRecorderFailed;
345 
BadSharingTestState(TestModule testModule, boolean playerFailed, boolean recorderFailed)346             BadSharingTestState(TestModule testModule,
347                                 boolean playerFailed, boolean recorderFailed) {
348                 super(testModule);
349 
350                 mPlayerFailed = playerFailed;
351                 mRecorderFailed = recorderFailed;
352             }
353 
buildErrorString(TestModule testModule)354             String buildErrorString(TestModule testModule) {
355                 StringBuilder sb = new StringBuilder();
356 
357                 if (testModule.mTransferType == TRANSFER_LEGACY) {
358                     sb.append(" SKIP: can't set LEGACY mode");
359                 } else if (testModule.mTransferType == TRANSFER_MMAP_EXCLUSIVE) {
360                     sb.append(" SKIP: can't set MMAP EXCLUSIVE mode");
361                 } else {
362                     sb.append(" SKIP: can't set MMAP SHARED mode");
363                 }
364 
365                 sb.append(" - ");
366                 if (mPlayerFailed) {
367                     sb.append("Player");
368                     if (mRecorderFailed) {
369                         sb.append("|");
370                     }
371                 }
372                 if (mRecorderFailed) {
373                     sb.append("Recorder");
374                 }
375                 return sb.toString();
376             }
377         }
378 
379         class BadMMAPTestState extends TestStateData {
380             final boolean mPlayerFailed;
381             final boolean mRecorderFailed;
382 
BadMMAPTestState(TestModule testModule, boolean playerFailed, boolean recorderFailed)383             BadMMAPTestState(TestModule testModule,
384                                 boolean playerFailed, boolean recorderFailed) {
385                 super(testModule);
386 
387                 mPlayerFailed = playerFailed;
388                 mRecorderFailed = recorderFailed;
389             }
390 
buildErrorString(TestModule testModule)391             String buildErrorString(TestModule testModule) {
392                 StringBuilder sb = new StringBuilder();
393                 sb.append(" Didn't get MMAP");
394                 sb.append(" - ");
395                 if (mPlayerFailed) {
396                     sb.append("Player");
397                     if (mRecorderFailed) {
398                         sb.append("|");
399                     }
400                 }
401                 if (mRecorderFailed) {
402                     sb.append("Recorder");
403                 }
404                 return sb.toString();
405             }
406         }
407 
TestModule(int outDeviceType, int outSampleRate, int outChannelCount, int inDeviceType, int inSampleRate, int inChannelCount)408         TestModule(int outDeviceType, int outSampleRate, int outChannelCount,
409                    int inDeviceType, int inSampleRate, int inChannelCount) {
410             mOutDeviceType = outDeviceType;
411             mOutSampleRate = outSampleRate;
412             mOutChannelCount = outChannelCount;
413 
414             // Default
415             mInDeviceType = inDeviceType;
416             mInChannelCount = inChannelCount;
417             mInSampleRate = inSampleRate;
418 
419             initializeTestState();
420         }
421 
initializeTestState()422         private void initializeTestState() {
423             mTestStateCode = new int[NUM_TEST_APIS];
424             mTestStateData = new TestStateData[NUM_TEST_APIS];
425             for (int api = 0; api < NUM_TEST_APIS; api++) {
426                 mTestStateCode[api] = TestModule.TESTSTATUS_NOT_RUN;
427             }
428             mTestResults = new TestResults[NUM_TEST_APIS];
429         }
430 
431         /**
432          * We need a more-or-less deep copy so as not to share mTestState and mTestResults
433          * arrays. We are only using this to setup closely related test modules, so it is
434          * sufficient to initialize mTestState and mTestResults to their "not tested" states.
435          *
436          * @return The (mostly) cloned TestModule object.
437          */
438         @Override
clone()439         public TestModule clone() throws CloneNotSupportedException {
440             // this will clone all the simple data members
441             TestModule clonedModule = (TestModule) super.clone();
442 
443             // Each clone needs it own set of states and results
444             clonedModule.initializeTestState();
445 
446             return clonedModule;
447         }
448 
getModuleIndex()449         public int getModuleIndex() {
450             return mModuleIndex;
451         }
452 
setModuleIndex(int index)453         public void setModuleIndex(int index) {
454             this.mModuleIndex = index;
455         }
456 
setSavedFileMessage(String s)457         public void setSavedFileMessage(String s) {
458             mSavedFileMessage = s;
459         }
460 
461         /**
462          * A message generated when saving a WAV file.
463          *
464          * @return message or null
465          */
getSavedFileMessage()466         public String getSavedFileMessage() {
467             return mSavedFileMessage;
468         }
469 
setAnalysisType(int type)470         public void setAnalysisType(int type) {
471             mAnalysisType = type;
472         }
473 
474         // Test states that indicate a not run or successful (not failures) test are
475         // zero or positive
476         // Test states that indicate an executed test that failed are negative.
477         public static final int TESTSTATUS_NOT_RUN = 1;
478         public static final int TESTSTATUS_RUN = 0;
479         public static final int TESTSTATUS_BAD_START = -1;
480         public static final int TESTSTATUS_BAD_ROUTING = -2;
481         public static final int TESTSTATUS_BAD_ANALYSIS_CHANNEL = -3;
482         public static final int TESTSTATUS_CANT_SET_MMAP = -4;
483         public static final int TESTSTATUS_BAD_SHARINGMODE = -5;
484         public static final int TESTSTATUS_MISMATCH_MMAP = -6;  // we didn't get the MMAP mode
485                                                                 // we asked for
486         public static final int TESTSTATUS_BAD_BUILD = -7;
487 
clearTestState(int api)488         void clearTestState(int api) {
489             mTestStateCode[api] = TESTSTATUS_NOT_RUN;
490             mTestResults[api] = null;
491             mTestHasBeenRun = false;
492             mTestCanceledByUser = false;
493         }
494 
getTestState(int api)495         int getTestState(int api) {
496             return mTestStateCode[api];
497         }
498 
setTestState(int api, int state, TestStateData data)499         int setTestState(int api, int state, TestStateData data) {
500             mTestStateData[api] = data;
501             return mTestStateCode[api] = state;
502         }
503 
getOutDeviceName()504         String getOutDeviceName() {
505             return AudioDeviceUtils.getShortDeviceTypeName(mOutDeviceType);
506         }
507 
getInDeviceName()508         String getInDeviceName() {
509             return AudioDeviceUtils.getShortDeviceTypeName(mInDeviceType);
510         }
511 
setSectionTitle(String title)512         void setSectionTitle(String title) {
513             mSectionTitle = title;
514         }
515 
getSectionTitle()516         String getSectionTitle() {
517             return mSectionTitle;
518         }
519 
setDescription(String description)520         void setDescription(String description) {
521             mDescription = description;
522         }
523 
getDescription()524         String getDescription() {
525             return "(" + getModuleIndex() + ") " + mDescription
526                     + "-" + transferTypeToString(mTransferType)
527                     + ":" + performanceModeToString(mOutPerformanceMode);
528         }
529 
setAnalysisChannel(int channel)530         void setAnalysisChannel(int channel) {
531             mAnalysisChannel = channel;
532         }
533 
setSources(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider)534         void setSources(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) {
535             mSourceProvider = sourceProvider;
536             mSinkProvider = sinkProvider;
537         }
538 
setInputPreset(int preset)539         void setInputPreset(int preset) {
540             mInputPreset = preset;
541         }
542 
setTransferType(int type)543         void setTransferType(int type) {
544             mTransferType = type;
545         }
546 
canRun()547         boolean canRun() {
548             return mInDeviceInfo != null && mOutDeviceInfo != null;
549         }
550 
setTestResults(int api, BaseSineAnalyzer analyzer)551         void setTestResults(int api, BaseSineAnalyzer analyzer) {
552             mTestResults[api] = new TestResults(api,
553                     analyzer.getMagnitude(),
554                     analyzer.getMaxMagnitude(),
555                     analyzer.getPhaseOffset(),
556                     analyzer.getPhaseJitter());
557         }
558 
559         //
560         // Predicates
561         //
562         // Ran to completion, maybe with failures
hasRun(int api)563         boolean hasRun(int api) {
564             return mTestStateCode[api] != TESTSTATUS_NOT_RUN;
565         }
566 
567         // Ran and passed the criteria
hasPassed(int api)568         boolean hasPassed(int api) {
569             boolean passed = false;
570             if (hasRun(api) && mTestResults[api] != null) {
571                 if (mAnalysisType == TYPE_SIGNAL_PRESENCE) {
572                     passed = mTestResults[api].mMaxMagnitude >= MIN_SIGNAL_PASS_MAGNITUDE
573                             && mTestResults[api].mPhaseJitter <= MAX_SIGNAL_PASS_JITTER;
574                 } else {
575                     passed = mTestResults[api].mMaxMagnitude <= MAX_XTALK_PASS_MAGNITUDE;
576                 }
577             }
578             return passed;
579         }
580 
581         // Should've been able to run, but ran into errors opening/starting streams
hasError(int api)582         boolean hasError(int api) {
583             // TESTSTATUS_NOT_RUN && TESTSTATUS_RUN are not errors
584             return mTestStateCode[api] < 0;
585         }
586 
587         //
588         // UI Helpers
589         //
transferTypeToString(int transferType)590         static String transferTypeToString(int transferType) {
591             switch (transferType) {
592                 case TRANSFER_LEGACY:
593                     return "Legacy";
594                 case TRANSFER_MMAP_SHARED:
595                     return "MMAP-Shared";
596                 case TRANSFER_MMAP_EXCLUSIVE:
597                     return "MMAP-Exclusive";
598                 default:
599                     return "Unknown Transfer Type [" + transferType + "]";
600             }
601         }
602 
transferTypeToSharingString(int transferType)603         static String transferTypeToSharingString(int transferType) {
604             switch (transferType) {
605                 case TRANSFER_LEGACY:
606                 case TRANSFER_MMAP_SHARED:
607                     return "Shared";
608                 case TRANSFER_MMAP_EXCLUSIVE:
609                     return "Exclusive";
610                 default:
611                     return "Unknown Transfer Type [" + transferType + "]";
612             }
613         }
614 
performanceModeToString(int performanceMode)615         String performanceModeToString(int performanceMode) {
616             switch (performanceMode) {
617                 case BuilderBase.PERFORMANCE_MODE_NONE:
618                     return getString(R.string.perf_mode_none_abreviation);
619                 case BuilderBase.PERFORMANCE_MODE_POWERSAVING:
620                     return getString(R.string.perf_mode_powersave_abreviation);
621                 case BuilderBase.PERFORMANCE_MODE_LOWLATENCY:
622                     return getString(R.string.perf_mode_lowlatency_abreviation);
623                 default:
624                     return getString(R.string.perf_mode_none_abreviation);
625             }
626         }
627 
628         // {device}:{channel}:{channelCount}:{SR}:{path}
629         // SpeakerSafe:0:2:48000:Legacy
formatOutputAttributes()630         String formatOutputAttributes() {
631             String deviceName = AudioDeviceUtils.getShortDeviceTypeName(mOutDeviceType);
632             return deviceName + ":" + mAnalysisChannel
633                     + ":" + mOutChannelCount
634                     + ":" + mOutSampleRate
635                     + ":" + transferTypeToString(mTransferType)
636                     + ":" + performanceModeToString(mOutPerformanceMode);
637         }
638 
formatInputAttributes()639         String formatInputAttributes() {
640             String deviceName = AudioDeviceUtils.getShortDeviceTypeName(mInDeviceType);
641             return deviceName + ":" + mAnalysisChannel
642                     + ":" + mInChannelCount
643                     + ":" + mInSampleRate
644                     + ":" + transferTypeToString(mTransferType);
645         }
646 
getTestStateString(int api)647         String getTestStateString(int api) {
648             int state = getTestState(api);
649             switch (state) {
650                 case TESTSTATUS_NOT_RUN:
651                     return " NOT TESTED";
652                 case TESTSTATUS_RUN:
653                     if (mTestResults[api] == null) {
654                         // This can happen when the test sequence is cancelled.
655                         return " NO RESULTS";
656                     } else {
657                         return hasPassed(api) ? " PASS" : " FAIL";
658                     }
659                 case TESTSTATUS_BAD_START:
660                     return " BAD START - Couldn't start streams";
661                 case TESTSTATUS_BAD_BUILD:
662                     return " BAD BUILD - Couldn't open streams";
663                 case TESTSTATUS_BAD_ROUTING:
664                     return " BAD ROUTE";
665                 case TESTSTATUS_BAD_ANALYSIS_CHANNEL:
666                     return " BAD ANALYSIS CHANNEL";
667                 case TESTSTATUS_CANT_SET_MMAP:
668                 case TESTSTATUS_MISMATCH_MMAP: {
669                     BadMMAPTestState errorData = (BadMMAPTestState) mTestStateData[api];
670                     return errorData.buildErrorString(this);
671                 }
672                 case TESTSTATUS_BAD_SHARINGMODE: {
673                     BadSharingTestState errorData = (BadSharingTestState) mTestStateData[api];
674                     return errorData.buildErrorString(this);
675                 }
676                 default:
677                     return " UNKNOWN STATE ID [" + state + "]";
678             }
679         }
680 
logBeginning(int api)681         private void logBeginning(int api) {
682             Log.d(TAG, "BEGIN_SUB_TEST: " + getDescription() + ", " + audioApiToString(api));
683         }
684 
logEnding(int api)685         private void logEnding(int api) {
686             Log.d(TAG, "END_SUB_TEST: " + getDescription()
687                     + ", " + audioApiToString(api)
688                     + "," + getTestStateString(api)); // has leading space!
689         }
690 
691         //
692         // Process
693         //
694         // TEMP
startTest(int api)695         private int startTest(int api) {
696             logBeginning(api);
697             if (mOutDeviceInfo != null && mInDeviceInfo != null) {
698                 mAnalyzer.reset();
699                 mAnalyzer.setSampleRate(mInSampleRate);
700                 if (mAnalysisChannel < mInChannelCount) {
701                     mAnalyzer.setInputChannel(mAnalysisChannel);
702                 } else {
703                     Log.e(TAG, "Invalid analysis channel " + mAnalysisChannel
704                             + " for " + mInChannelCount + " input signal.");
705                     return setTestState(api, TESTSTATUS_BAD_ANALYSIS_CHANNEL, null);
706                 }
707 
708                 // Player
709                 mDuplexAudioManager.setSources(mSourceProvider, mSinkProvider);
710                 mDuplexAudioManager.setPlayerRouteDevice(mOutDeviceInfo);
711                 mDuplexAudioManager.setPlayerSampleRate(mOutSampleRate);
712                 mDuplexAudioManager.setNumPlayerChannels(mOutChannelCount);
713                 mDuplexAudioManager.setPlayerSharingMode(mTransferType == TRANSFER_MMAP_EXCLUSIVE
714                         ? BuilderBase.SHARING_MODE_EXCLUSIVE : BuilderBase.SHARING_MODE_SHARED);
715                 mDuplexAudioManager.setPlayerPerformanceMode(mOutPerformanceMode);
716 
717                 // Recorder
718                 mDuplexAudioManager.setRecorderRouteDevice(mInDeviceInfo);
719                 mDuplexAudioManager.setInputPreset(mInputPreset);
720                 mDuplexAudioManager.setRecorderSampleRate(mInSampleRate);
721                 mDuplexAudioManager.setNumRecorderChannels(mInChannelCount);
722                 mDuplexAudioManager.setRecorderSharingMode(mTransferType == TRANSFER_MMAP_EXCLUSIVE
723                         ? BuilderBase.SHARING_MODE_EXCLUSIVE : BuilderBase.SHARING_MODE_SHARED);
724                 mDuplexAudioManager.setRecorderPerformanceMode(mInPerformanceMode);
725 
726                 boolean enableMMAP = mTransferType != TRANSFER_LEGACY;
727                 Globals.setMMapEnabled(enableMMAP);
728                 // This should never happen as MMAP TestModules will not get allocated
729                 // in the case that MMAP isn't supported on the device.
730                 // See addTestModule() and initialization of
731                 // mSupportsMMAP and mSupportsMMAPExclusive.
732                 if (Globals.isMMapEnabled() != enableMMAP) {
733                     Log.d(TAG, "  Invalid MMAP request - " + getDescription());
734                     Globals.setMMapEnabled(Globals.isMMapSupported());
735                     return setTestState(api, TESTSTATUS_CANT_SET_MMAP,
736                             new BadMMAPTestState(this, false, false));
737                 }
738                 try {
739                     // Open the streams.
740                     // Note AudioSources and AudioSinks get allocated at this point
741                     int errorCode = mDuplexAudioManager.buildStreams(mAudioApi, mAudioApi);
742                     if (errorCode != StreamBase.OK) {
743                         Log.e(TAG, "  mDuplexAudioManager.buildStreams() failed error:"
744                                 + errorCode);
745                         return setTestState(api, TESTSTATUS_BAD_BUILD, null);
746                     }
747                 } finally {
748                     // handle the failure here...
749                     Globals.setMMapEnabled(Globals.isMMapSupported());
750                 }
751 
752                 // (potentially) Adjust AudioSource parameters
753                 AudioSource audioSource = mSourceProvider.getActiveSource();
754 
755                 // Set the sample rate for the source (the sample rate for the player gets
756                 // set in the DuplexAudioManager.Builder.
757                 audioSource.setSampleRate(mOutSampleRate);
758 
759                 // Adjust the player frequency to match with the quantized frequency
760                 // of the analyzer.
761                 audioSource.setFreq((float) mAnalyzer.getAdjustedFrequency());
762 
763                 mWaveView.setNumChannels(mInChannelCount);
764 
765                 // Validate Sharing Mode
766                 boolean playerSharingModeVerified =
767                         mDuplexAudioManager.isSpecifiedPlayerSharingMode();
768                 boolean recorderSharingModeVerified =
769                         mDuplexAudioManager.isSpecifiedRecorderSharingMode();
770                 if (!playerSharingModeVerified || !recorderSharingModeVerified) {
771                     Log.w(TAG, "  Invalid Sharing Mode - " + getDescription());
772                     return setTestState(api, TESTSTATUS_BAD_SHARINGMODE,
773                             new BadSharingTestState(this,
774                                     !playerSharingModeVerified,
775                                     !recorderSharingModeVerified));
776                 }
777 
778                 // Validate MMAP
779                 boolean playerIsMMap = false;
780                 boolean recorderIsMMap = false;
781                 if (mTransferType != TRANSFER_LEGACY) {
782                     // This is (should be) an MMAP stream
783                     playerIsMMap = mDuplexAudioManager.isPlayerStreamMMap();
784                     recorderIsMMap = mDuplexAudioManager.isRecorderStreamMMap();
785 
786                     if (!playerIsMMap && !recorderIsMMap) {
787                         Log.w(TAG, "  Neither stream is MMAP - " + getDescription());
788                         return setTestState(api, TESTSTATUS_MISMATCH_MMAP,
789                                 new BadMMAPTestState(this, !playerIsMMap, !recorderIsMMap));
790                     }
791                 }
792 
793                 if (mDuplexAudioManager.start() != StreamBase.OK) {
794                     Log.e(TAG, "  Couldn't start duplex streams - " + getDescription());
795                     return setTestState(api, TESTSTATUS_BAD_START, null);
796                 }
797 
798                 // Validate routing
799                 if (!mDuplexAudioManager.validateRouting()) {
800                     Log.w(TAG, "  Invalid Routing - " + getDescription());
801                     return setTestState(api, TESTSTATUS_BAD_ROUTING, null);
802                 }
803 
804                 BadMMAPTestState mmapState = null;
805                 if (mTransferType != TRANSFER_LEGACY && (!playerIsMMap || !recorderIsMMap)) {
806                     // asked for MMAP, but at least one route is Legacy
807                     Log.w(TAG, "  Both streams aren't MMAP - " + getDescription());
808                     mmapState = new BadMMAPTestState(this, !playerIsMMap, !recorderIsMMap);
809                 }
810 
811                 return setTestState(api, TESTSTATUS_RUN, mmapState);
812             }
813 
814             return setTestState(api, TESTSTATUS_NOT_RUN, null);
815         }
816 
advanceTestPhase(int api)817         int advanceTestPhase(int api) {
818             return 0;
819         }
820 
821         //
822         // HTML Reporting
823         //
generateReport(int api, TextFormatter textFormatter)824         TextFormatter generateReport(int api, TextFormatter textFormatter) {
825             // Description
826             textFormatter.openParagraph()
827                     .appendText(getDescription());
828 
829             // Result
830             int state = getTestState(api);
831             if (state != TESTSTATUS_NOT_RUN) {
832                 textFormatter.appendBreak()
833                         .openBold()
834                         .appendText(getTestStateString(api))
835                         .closeBold();
836             }
837 
838             // Any Data?
839             TestResults results = mTestResults[api];    // need this (potentially) below.
840             TestStateData stateData = mTestStateData[api];
841             if (results != null && stateData != null) {
842                 textFormatter.appendBreak()
843                         .openTextColor("blue")
844                         .appendText(stateData.buildErrorString(this))
845                         .closeTextColor();
846             }
847 
848             // Report Error
849             if (hasRun(api) && hasError(api)) {
850                 textFormatter.appendBreak();
851                 switch (mTestStateCode[api]) {
852                     case TESTSTATUS_BAD_START:
853                         textFormatter.openTextColor("red");
854                         textFormatter.appendText("Error : Couldn't Start Stream");
855                         textFormatter.closeTextColor();
856                         break;
857                     case TESTSTATUS_BAD_BUILD:
858                         textFormatter.openTextColor("red");
859                         textFormatter.appendText("Error : Couldn't Open Stream");
860                         textFormatter.closeTextColor();
861                         break;
862                     case TESTSTATUS_BAD_ROUTING:
863                         textFormatter.openTextColor("red");
864                         textFormatter.appendText("Error : Invalid Route");
865                         textFormatter.closeTextColor();
866                         break;
867                     case TESTSTATUS_BAD_ANALYSIS_CHANNEL:
868                         textFormatter.openTextColor("red");
869                         textFormatter.appendText("Error : Invalid Analysis Channel");
870                         textFormatter.closeTextColor();
871                         break;
872                     case TESTSTATUS_CANT_SET_MMAP:
873                         textFormatter.openTextColor("red");
874                         textFormatter.appendText("Error : Did not set MMAP mode - "
875                                 + transferTypeToSharingString(mTransferType));
876                         textFormatter.closeTextColor();
877                         break;
878                     case TESTSTATUS_MISMATCH_MMAP: {
879                         textFormatter.openTextColor("blue");
880                         textFormatter.appendText("Note : ");
881                         BadMMAPTestState errorData = (BadMMAPTestState) mTestStateData[api];
882                         String transferTypeString = transferTypeToSharingString(mTransferType);
883                         if (errorData.mPlayerFailed) {
884                             textFormatter.appendText(PLAYER_FAILED_TO_GET_STRING
885                                     + transferTypeString);
886                             textFormatter.appendBreak();
887                             textFormatter.appendText(formatOutputAttributes());
888                         }
889                         if (errorData.mRecorderFailed) {
890                             if (errorData.mPlayerFailed) {
891                                 textFormatter.appendBreak();
892                             }
893                             textFormatter.appendText(RECORDER_FAILED_TO_GET_STRING
894                                     + transferTypeString);
895                             textFormatter.appendBreak();
896                             textFormatter.appendText(formatInputAttributes());
897                         }
898                         textFormatter.closeTextColor();
899                     }
900                         break;
901                     case TESTSTATUS_BAD_SHARINGMODE:
902                         textFormatter.openTextColor("blue");
903                         textFormatter.appendText("Note : ");
904                         BadSharingTestState errorData =
905                                 (BadSharingTestState) mTestStateData[api];
906                         String transferTypeString = transferTypeToSharingString(mTransferType);
907                         if (errorData.mPlayerFailed) {
908                             textFormatter.appendText(PLAYER_FAILED_TO_GET_STRING
909                                     + transferTypeString);
910                         }
911                         if (errorData.mRecorderFailed) {
912                             textFormatter.appendText(RECORDER_FAILED_TO_GET_STRING
913                                     + transferTypeString);
914                         }
915                         textFormatter.appendBreak();
916                         textFormatter.appendText(formatOutputAttributes());
917                         textFormatter.closeTextColor();
918                         break;
919                 }
920                 textFormatter.closeTextColor();
921             }
922 
923             // Results Data
924             if (results != null) {
925                 // We (attempted to) run this module. Let's see how it turned out.
926                 // we can get null here if the test was cancelled
927                 Locale locale = Locale.getDefault();
928                 String maxMagString = String.format(
929                         locale, "mag:%.5f ", results.mMaxMagnitude);
930                 String phaseJitterString = String.format(
931                         locale, "jitter:%.5f ", results.mPhaseJitter);
932 
933                 boolean passMagnitude = mAnalysisType == TYPE_SIGNAL_PRESENCE
934                         ? results.mMaxMagnitude >= MIN_SIGNAL_PASS_MAGNITUDE
935                         : results.mMaxMagnitude <= MAX_XTALK_PASS_MAGNITUDE;
936 
937                 // Values / Criteria
938                 // NOTE: The criteria is why the test passed or failed, not what
939                 // was needed to pass.
940                 // So, for a cross-talk test, "mag:0.01062 > 0.01000" means that the test
941                 // failed, because 0.01062 > 0.01000
942                 textFormatter.appendBreak();
943                 textFormatter.openTextColor(passMagnitude ? "black" : "red");
944                 if (mAnalysisType == TYPE_SIGNAL_PRESENCE) {
945                     textFormatter.appendText(maxMagString
946                             + String.format(locale,
947                             passMagnitude ? " >= %.5f " : " < %.5f ",
948                             MIN_SIGNAL_PASS_MAGNITUDE));
949                 } else {
950                     textFormatter.appendText(maxMagString
951                             + String.format(locale,
952                             passMagnitude ? " <= %.5f " : " > %.5f ",
953                             MAX_XTALK_PASS_MAGNITUDE));
954                 }
955                 textFormatter.closeTextColor();
956 
957                 if (mAnalysisType == TYPE_SIGNAL_PRESENCE) {
958                     // Do we want a threshold value for jitter in crosstalk tests?
959                     boolean passJitter =
960                             results.mPhaseJitter <= MAX_SIGNAL_PASS_JITTER;
961                     textFormatter.openTextColor(passJitter ? "black" : "red");
962                     textFormatter.appendText(phaseJitterString
963                             + String.format(locale, passJitter ? " <= %.5f" : " > %.5f",
964                             MAX_SIGNAL_PASS_JITTER));
965                     textFormatter.closeTextColor();
966                 } else {
967                     textFormatter.appendText(phaseJitterString);
968                 }
969 
970                 textFormatter.appendBreak();
971 
972                 // "Prose" status messages
973                 textFormatter.openItalic();
974                 if (mAnalysisType == TYPE_SIGNAL_PRESENCE) {
975                     if (results.mMaxMagnitude == 0.0) {
976                         textFormatter.appendText("Dead Channel?");
977                     } else if (results.mMaxMagnitude > 0.0
978                             && results.mMaxMagnitude < MIN_SIGNAL_PASS_MAGNITUDE) {
979                         textFormatter.appendText("Low Gain or Volume.");
980                     } else if (results.mPhaseJitter > MAX_SIGNAL_PASS_JITTER) {
981                         // if the signal is absent or really low, the jitter will be high, so
982                         // only call out a high jitter if there seems to be a reasonable signal.
983                         textFormatter.appendText("Noisy or Corrupt Signal.");
984                     }
985                 } else {
986                     // TYPE_SIGNAL_ABSENCE
987                     if (results.mMaxMagnitude > MAX_XTALK_PASS_MAGNITUDE) {
988                         textFormatter.appendText("Cross Talk Failed. "
989                                 + "Crossed patch cables on interface?");
990                     }
991                 }
992                 textFormatter.closeItalic();
993 
994                 String savedFileMessage = getSavedFileMessage();
995                 if (savedFileMessage != null) {
996                     textFormatter.appendBreak().appendText(savedFileMessage);
997                 }
998             } else {
999                 // results == null
1000                 textFormatter.appendBreak()
1001                         .openBold()
1002                         .appendText("Skipped.")
1003                         .closeBold();
1004             }
1005 
1006             textFormatter.closeParagraph();
1007 
1008             return textFormatter;
1009         }
1010 
1011         //
1012         // CTS VerifierReportLog stuff
1013         //
1014         // ReportLog Schema
1015         private static final String KEY_TESTDESCRIPTION = "test_description";
1016         // Output Specification
1017         private static final String KEY_OUT_DEVICE_TYPE = "out_device_type";
1018         private static final String KEY_OUT_DEVICE_NAME = "out_device_name";
1019         private static final String KEY_OUT_DEVICE_RATE = "out_device_rate";
1020         private static final String KEY_OUT_DEVICE_CHANS = "out_device_chans";
1021 
1022         // Input Specification
1023         private static final String KEY_IN_DEVICE_TYPE = "in_device_type";
1024         private static final String KEY_IN_DEVICE_NAME = "in_device_name";
1025         private static final String KEY_IN_DEVICE_RATE = "in_device_rate";
1026         private static final String KEY_IN_DEVICE_CHANS = "in_device_chans";
1027         private static final String KEY_IN_PRESET = "in_preset";
1028 
generateReportLog(int api)1029         void generateReportLog(int api) {
1030             if (!canRun() || mTestResults[api] == null) {
1031                 return;
1032             }
1033 
1034             CtsVerifierReportLog reportLog = newReportLog();
1035 
1036             // Description
1037             reportLog.addValue(
1038                     KEY_TESTDESCRIPTION,
1039                     getDescription(),
1040                     ResultType.NEUTRAL,
1041                     ResultUnit.NONE);
1042 
1043             // Output Specification
1044             reportLog.addValue(
1045                     KEY_OUT_DEVICE_NAME,
1046                     getOutDeviceName(),
1047                     ResultType.NEUTRAL,
1048                     ResultUnit.NONE);
1049 
1050             reportLog.addValue(
1051                     KEY_OUT_DEVICE_TYPE,
1052                     mOutDeviceType,
1053                     ResultType.NEUTRAL,
1054                     ResultUnit.NONE);
1055 
1056             reportLog.addValue(
1057                     KEY_OUT_DEVICE_RATE,
1058                     mOutSampleRate,
1059                     ResultType.NEUTRAL,
1060                     ResultUnit.NONE);
1061 
1062             reportLog.addValue(
1063                     KEY_OUT_DEVICE_CHANS,
1064                     mOutChannelCount,
1065                     ResultType.NEUTRAL,
1066                     ResultUnit.NONE);
1067 
1068             // Input Specifications
1069             reportLog.addValue(
1070                     KEY_IN_DEVICE_NAME,
1071                     getInDeviceName(),
1072                     ResultType.NEUTRAL,
1073                     ResultUnit.NONE);
1074 
1075             reportLog.addValue(
1076                     KEY_IN_DEVICE_TYPE,
1077                     mInDeviceType,
1078                     ResultType.NEUTRAL,
1079                     ResultUnit.NONE);
1080 
1081             reportLog.addValue(
1082                     KEY_IN_DEVICE_RATE,
1083                     mInSampleRate,
1084                     ResultType.NEUTRAL,
1085                     ResultUnit.NONE);
1086 
1087             reportLog.addValue(
1088                     KEY_IN_DEVICE_CHANS,
1089                     mInChannelCount,
1090                     ResultType.NEUTRAL,
1091                     ResultUnit.NONE);
1092 
1093             reportLog.addValue(
1094                     KEY_IN_PRESET,
1095                     mInputPreset,
1096                     ResultType.NEUTRAL,
1097                     ResultUnit.NONE);
1098 
1099             // Results
1100             mTestResults[api].generateReportLog(reportLog);
1101 
1102             reportLog.submit();
1103         }
1104     }
1105 
1106     /*
1107      * TestResults
1108      */
1109     class TestResults {
1110         int mApi;
1111         double mMagnitude;
1112         double mMaxMagnitude;
1113         double mPhase;
1114         double mPhaseJitter;
1115 
TestResults(int api, double magnitude, double maxMagnitude, double phase, double phaseJitter)1116         TestResults(int api, double magnitude, double maxMagnitude, double phase,
1117                     double phaseJitter) {
1118             mApi = api;
1119             mMagnitude = magnitude;
1120             mMaxMagnitude = maxMagnitude;
1121             mPhase = phase;
1122             mPhaseJitter = phaseJitter;
1123         }
1124 
1125         // ReportLog Schema
1126         private static final String KEY_TESTAPI = "test_api";
1127         private static final String KEY_MAXMAGNITUDE = "max_magnitude";
1128         private static final String KEY_PHASEJITTER = "phase_jitter";
1129 
generateReportLog(CtsVerifierReportLog reportLog)1130         void generateReportLog(CtsVerifierReportLog reportLog) {
1131             reportLog.addValue(
1132                     KEY_TESTAPI,
1133                     mApi,
1134                     ResultType.NEUTRAL,
1135                     ResultUnit.NONE);
1136 
1137             reportLog.addValue(
1138                     KEY_MAXMAGNITUDE,
1139                     mMaxMagnitude,
1140                     ResultType.NEUTRAL,
1141                     ResultUnit.NONE);
1142 
1143             reportLog.addValue(
1144                     KEY_PHASEJITTER,
1145                     mPhaseJitter,
1146                     ResultType.NEUTRAL,
1147                     ResultUnit.NONE);
1148         }
1149     }
1150 
gatherTestModules(TestManager testManager)1151     abstract void gatherTestModules(TestManager testManager);
1152 
postValidateTestDevices(int numValidTestModules)1153     abstract void postValidateTestDevices(int numValidTestModules);
1154 
1155     /*
1156      * TestManager
1157      */
1158     class TestManager {
1159         static final String TAG = "TestManager";
1160 
1161         // Audio Device Type ID -> TestProfile
1162         private ArrayList<TestModule> mTestModules = new ArrayList<TestModule>();
1163 
1164         public int mApi;
1165 
1166         private int    mPhaseCount;
1167 
1168         // which route are we running
1169         static final int TESTSTEP_NONE = -1;
1170         private int mTestStep = TESTSTEP_NONE;
1171 
1172         private Timer mTimer;
1173 
initializeTests()1174         public void initializeTests() {
1175             // Get the test modules from the sub-class
1176             clearTestModules();
1177             gatherTestModules(this);
1178 
1179             validateTestDevices();
1180             displayTestDevices();
1181         }
1182 
clearTestState()1183         public void clearTestState() {
1184             for (TestModule module: mTestModules) {
1185                 module.clearTestState(mApi);
1186             }
1187         }
1188 
clearTestModules()1189         public void clearTestModules() {
1190             mTestModules.clear();
1191         }
1192 
addIndexedTestModule(TestModule module)1193         private void addIndexedTestModule(TestModule module) {
1194             module.setModuleIndex(mTestModules.size());
1195             mTestModules.add(module);
1196         }
1197 
addTestModule(TestModule module)1198         public void addTestModule(TestModule module) {
1199             // We're going to expand each module to three, one for each transfer type
1200 
1201             //
1202             // BuilderBase.PERFORMANCE_MODE_NONE
1203             //
1204             module.setTransferType(TestModule.TRANSFER_LEGACY);
1205             // Test Performance Mode None for both Output and Input
1206             module.mOutPerformanceMode = module.mInPerformanceMode =
1207                     BuilderBase.PERFORMANCE_MODE_NONE;
1208             addIndexedTestModule(module);
1209 
1210             //
1211             // BuilderBase.PERFORMANCE_MODE_LOWLATENCY
1212             //
1213             try {
1214                 // Expand out to PerformanceMode.None & PerformanceMode.LowLatency
1215                 TestModule clonedModule = module.clone();
1216                 // Test Performance Mode LowLatency for both Output and Input
1217                 clonedModule.mOutPerformanceMode = module.mInPerformanceMode =
1218                         BuilderBase.PERFORMANCE_MODE_LOWLATENCY;
1219                 clonedModule.mSectionTitle = null;
1220                 addIndexedTestModule(clonedModule);
1221             } catch (CloneNotSupportedException ex) {
1222                 Log.e(TAG, "Couldn't clone TestModule - PERFORMANCE_MODE_LOWLATENCY");
1223             }
1224 
1225             //
1226             // MMAP Modes - BuilderBase.PERFORMANCE_MODE_LOWLATENCY
1227             // Note: Java API doesn't support MMAP Modes
1228             //
1229             if (mSupportsMMAP && mApi == TEST_API_NATIVE) {
1230                 try {
1231                     TestModule moduleMMAP = module.clone();
1232                     moduleMMAP.setTransferType(TestModule.TRANSFER_MMAP_SHARED);
1233                     // Test Performance Mode LowLatency for both Output and Input
1234                     moduleMMAP.mOutPerformanceMode = module.mInPerformanceMode =
1235                             BuilderBase.PERFORMANCE_MODE_LOWLATENCY;
1236                     addIndexedTestModule(moduleMMAP);
1237                     moduleMMAP.mSectionTitle = null;
1238                 } catch (CloneNotSupportedException ex) {
1239                     Log.e(TAG, "Couldn't clone TestModule - TRANSFER_MMAP_SHARED");
1240                 }
1241             }
1242 
1243             // Note: Java API doesn't support MMAP Modes
1244             if (mSupportsMMAPExclusive && mApi == TEST_API_NATIVE) {
1245                 try {
1246                     TestModule moduleExclusive = module.clone();
1247                     moduleExclusive.setTransferType(TestModule.TRANSFER_MMAP_EXCLUSIVE);
1248                     // Test Performance Mode LowLatency for both Output and Input
1249                     moduleExclusive.mOutPerformanceMode = module.mInPerformanceMode =
1250                             BuilderBase.PERFORMANCE_MODE_LOWLATENCY;
1251                     addIndexedTestModule(moduleExclusive);
1252                     moduleExclusive.mSectionTitle = null;
1253                 } catch (CloneNotSupportedException ex) {
1254                     Log.e(TAG, "Couldn't clone TestModule - TRANSFER_MMAP_EXCLUSIVE");
1255                 }
1256             }
1257         }
1258 
validateTestDevices()1259         public void validateTestDevices() {
1260             // do we have the output device we need
1261             AudioDeviceInfo[] outputDevices =
1262                     mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
1263             for (TestModule testModule : mTestModules) {
1264                 testModule.mOutDeviceInfo = null;
1265                 // Check to see if we have a (physical) device of this type
1266                 for (AudioDeviceInfo devInfo : outputDevices) {
1267                     // Don't invalidate previously validated devices
1268                     // Tests that test multiple device instances (like USB headset/interface)
1269                     // need to remember what devices are valid after being disconnected
1270                     // in order to connect the next device instance.
1271                     if (testModule.mOutDeviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
1272                             && !mHasSpeaker) {
1273                         break;
1274                     } else if (testModule.mOutDeviceType == devInfo.getType()) {
1275                         testModule.mOutDeviceInfo = devInfo;
1276                         break;
1277                     }
1278                 }
1279             }
1280 
1281             // do we have the input device we need
1282             AudioDeviceInfo[] inputDevices =
1283                     mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
1284             for (TestModule testModule : mTestModules) {
1285                 testModule.mInDeviceInfo = null;
1286                 // Check to see if we have a (physical) device of this type
1287                 for (AudioDeviceInfo devInfo : inputDevices) {
1288                     // Don't invalidate previously validated devices?
1289                     // See comment above.
1290                     if (testModule.mInDeviceType == AudioDeviceInfo.TYPE_BUILTIN_MIC
1291                             && !mHasMic) {
1292                         break;
1293                     } else if (testModule.mInDeviceType == devInfo.getType()) {
1294                         testModule.mInDeviceInfo = devInfo;
1295                         break;
1296                     }
1297                 }
1298             }
1299 
1300             // Is the Transfer Mode valid for this API?
1301             for (TestModule testModule : mTestModules) {
1302                 if (mApi == TEST_API_JAVA
1303                         && testModule.mTransferType != TestModule.TRANSFER_LEGACY) {
1304                     // MMAP transfer modes are not supported on JAVA
1305                     testModule.mInDeviceInfo = null;
1306                     testModule.mOutDeviceInfo = null;
1307                 }
1308             }
1309 
1310             postValidateTestDevices(countValidTestModules());
1311         }
1312 
getNumTestModules()1313         public int getNumTestModules() {
1314             return mTestModules.size();
1315         }
1316 
countValidTestModules()1317         public int countValidTestModules() {
1318             int numValid = 0;
1319             for (TestModule testModule : mTestModules) {
1320                 if (testModule.mOutDeviceInfo != null && testModule.mInDeviceInfo != null
1321                         // ignore MMAP Failures
1322                         && testModule.mTestStateCode[mApi] != TestModule.TESTSTATUS_MISMATCH_MMAP
1323                         && testModule.mTestStateCode[mApi]
1324                             != TestModule.TESTSTATUS_BAD_SHARINGMODE) {
1325                     numValid++;
1326                 }
1327             }
1328             return numValid;
1329         }
1330 
countValidOrPassedTestModules()1331         public int countValidOrPassedTestModules() {
1332             int numValid = 0;
1333             for (TestModule testModule : mTestModules) {
1334                 if ((testModule.mOutDeviceInfo != null && testModule.mInDeviceInfo != null)
1335                         || testModule.hasPassed(mApi)) {
1336                     numValid++;
1337                 }
1338             }
1339             return numValid;
1340         }
1341 
countTestedTestModules()1342         public int countTestedTestModules() {
1343             int numTested = 0;
1344             for (TestModule testModule : mTestModules) {
1345                 if (testModule.hasRun(mApi)) {
1346                     numTested++;
1347                 }
1348             }
1349             return numTested;
1350         }
1351 
displayTestDevices()1352         public void displayTestDevices() {
1353             StringBuilder sb = new StringBuilder();
1354             sb.append("Tests:");
1355             int testStep = 0;
1356             for (TestModule testModule : mTestModules) {
1357                 sb.append("\n");
1358                 if (testModule.getSectionTitle() != null) {
1359                     sb.append("---" + testModule.getSectionTitle() + "---\n");
1360                 }
1361                 if (testStep == mTestStep) {
1362                     sb.append(">>>");
1363                 }
1364                 sb.append(testModule.getDescription());
1365 
1366                 if (testModule.canRun() && testStep != mTestStep) {
1367                     sb.append(" *");
1368                 }
1369 
1370                 if (testStep == mTestStep) {
1371                     sb.append("<<<");
1372                 }
1373 
1374                 sb.append(testModule.getTestStateString(mApi));
1375 
1376                 String savedFileMessage = testModule.getSavedFileMessage();
1377                 if (savedFileMessage != null) {
1378                     sb.append("\n").append(savedFileMessage);
1379                 }
1380                 testStep++;
1381             }
1382             mRoutesTx.setText(sb.toString());
1383 
1384             showDeviceView();
1385         }
1386 
getActiveTestModule()1387         public TestModule getActiveTestModule() {
1388             return mTestStep != TESTSTEP_NONE && mTestStep < mTestModules.size()
1389                     ? mTestModules.get(mTestStep)
1390                     : null;
1391         }
1392 
countFailures(int api)1393         private int countFailures(int api) {
1394             int numFailed = 0;
1395             for (TestModule module : mTestModules) {
1396                 if (module.hasRun(api) // can only fail if it has run
1397                         && (module.hasError(api) || !module.hasPassed(api))) {
1398                     // Ignore MMAP "Inconsistencies"
1399                     // (we didn't get an MMAP stream so we skipped the test)
1400                     if (module.mTestStateCode[api]
1401                                 != TestModule.TESTSTATUS_MISMATCH_MMAP
1402                             && module.mTestStateCode[api]
1403                                 != TestModule.TESTSTATUS_BAD_SHARINGMODE) {
1404                         numFailed++;
1405                     }
1406                 }
1407             }
1408             return numFailed;
1409         }
1410 
startTest(TestModule testModule)1411         public int startTest(TestModule testModule) {
1412             if (mTestCanceledByUser) {
1413                 return TestModule.TESTSTATUS_NOT_RUN;
1414             }
1415 
1416             return testModule.startTest(mApi);
1417         }
1418 
1419         private static final int MS_PER_SEC = 1000;
1420         private static final int TEST_TIME_IN_SECONDS = 2;
startTest(int api)1421         public void startTest(int api) {
1422             showDeviceView();
1423 
1424             mApi = api;
1425 
1426             mTestStep = TESTSTEP_NONE;
1427             mTestCanceledByUser = false;
1428 
1429             mUtiltitiesHandler.setEnabled(false);
1430 
1431             (mTimer = new Timer()).scheduleAtFixedRate(new TimerTask() {
1432                 @Override
1433                 public void run() {
1434                     completeTestStep();
1435                     advanceTestModule();
1436                 }
1437             }, 0, TEST_TIME_IN_SECONDS * MS_PER_SEC);
1438         }
1439 
stopTest()1440         public void stopTest() {
1441             if (mTestStep != TESTSTEP_NONE) {
1442                 mTestStep = TESTSTEP_NONE;
1443 
1444                 if (mTimer != null) {
1445                     mTimer.cancel();
1446                     mTimer = null;
1447                 }
1448                 mDuplexAudioManager.stop();
1449             }
1450         }
1451 
calculatePass()1452         protected boolean calculatePass() {
1453             int numFailures = countFailures(mApi);
1454             int numUntested = countValidTestModules() - countTestedTestModules();
1455             return mTestHasBeenRun && !mTestCanceledByUser && numFailures == 0 && numUntested <= 0;
1456         }
1457 
generateResultsText(TextFormatter formatter)1458         public void generateResultsText(TextFormatter formatter) {
1459             formatter.clear();
1460             formatter.openDocument();
1461 
1462             formatter.openHeading(3)
1463                     .appendText(getTestCategory())
1464                     .closeHeading(3);
1465 
1466             mTestManager.generateReport(formatter);
1467 
1468             formatter.openParagraph();
1469             formatter.appendText("Audio Test Version: " + Common.VERSION_CODE);
1470             formatter.appendBreak();
1471             formatter.appendText("Android SDK Version: " + Build.VERSION.SDK_INT);
1472             formatter.appendBreak();
1473             formatter.appendText("- " + Build.MANUFACTURER + " - " + Build.MODEL);
1474             formatter.appendBreak().appendBreak();
1475 
1476             int numFailures = countFailures(mApi);
1477             int numUntested = getNumTestModules() - countTestedTestModules();
1478             formatter.appendText("Failure Count: " + numFailures);
1479             formatter.appendBreak();
1480             formatter.appendText("Untested Paths: " + numUntested);
1481 
1482             if (numFailures == 0 && numUntested == 0) {
1483                 formatter.appendBreak();
1484                 formatter.appendText("All tests passed.");
1485             }
1486             formatter.closeParagraph();
1487 
1488             formatter.openParagraph();
1489             formatter.appendText("Test Canceled: " + mTestCanceledByUser);
1490 
1491             if (mTestCanceledByUser) {
1492                 formatter.openBold()
1493                         .appendText("Please run the test sequence to completion.")
1494                         .closeBold()
1495                         .appendBreak()
1496                         .appendBreak();
1497             }
1498 
1499             // ALWAYS PASS (for now)
1500             mTestHasBeenRun = !mTestCanceledByUser;
1501             boolean passEnabled = passBtnEnabled();
1502             getPassButton().setEnabled(passEnabled);
1503 
1504             if (passEnabled) {
1505                 formatter.appendText("Although not all test modules passed, "
1506                         + "for this OS version you may press the ");
1507                 formatter.openBold();
1508                 formatter.appendText("PASS");
1509                 formatter.closeBold();
1510                 formatter.appendText(" button.");
1511                 formatter.appendBreak();
1512                 formatter.appendText("Note: In future versions, "
1513                         + "ALL test modules will be required to pass.");
1514                 formatter.appendBreak();
1515                 formatter.appendText("Note: Press the ");
1516                 formatter.openBold();
1517                 formatter.appendText("PASS");
1518                 formatter.closeBold();
1519                 formatter.appendText(" button below to complete the test.");
1520             }
1521             formatter.closeParagraph();
1522 
1523             formatter.closeDocument();
1524         }
1525 
completeTest()1526         public void completeTest() {
1527             runOnUiThread(new Runnable() {
1528                 @Override
1529                 public void run() {
1530                     enableTestButtons(true, false);
1531 
1532                     mRoutesTx.setVisibility(View.GONE);
1533                     mWaveView.setVisibility(View.GONE);
1534 
1535                     generateResultsText(mTextFormatter);
1536                     mTextFormatter.put(mResultsView);
1537 
1538                     showResultsView();
1539 
1540                     mUtiltitiesHandler.setEnabled(true);
1541                 }
1542             });
1543         }
1544 
completeTestStep()1545         public void completeTestStep() {
1546             if (mTestStep != TESTSTEP_NONE) {
1547                 mDuplexAudioManager.stop();
1548                 // Give the audio system a chance to settle from the previous state
1549                 // It is often the case that the Java API will not route to the specified
1550                 // device if we teardown/startup too quickly. This sleep cirmumvents that.
1551                 try {
1552                     Thread.sleep(500);
1553                 } catch (InterruptedException ex) {
1554                     Log.e(TAG, "sleep failed?");
1555                 }
1556 
1557                 TestModule testModule = getActiveTestModule();
1558                 if (testModule != null) {
1559                     if (testModule.canRun()) {
1560                         testModule.setTestResults(mApi, mAnalyzer);
1561                         if (!testModule.hasPassed(mApi)) {
1562                             String message = saveWaveFile(mAnalyzer, testModule.getModuleIndex());
1563                             testModule.setSavedFileMessage(message);
1564                         } else {
1565                             testModule.setSavedFileMessage(null); // erase any old messages
1566                         }
1567                         runOnUiThread(new Runnable() {
1568                             @Override
1569                             public void run() {
1570                                 displayTestDevices();
1571                                 mWaveView.resetPersistentMaxMagnitude();
1572                             }
1573                         });
1574                     }
1575                     testModule.logEnding(mApi);
1576                 }
1577             }
1578         }
1579 
advanceTestModule()1580         public void advanceTestModule() {
1581             Log.i(TAG, "advanceTestModule() user cancel:" + mTestCanceledByUser);
1582             if (mTestCanceledByUser) {
1583                 // test shutting down. Bail.
1584                 return;
1585             }
1586 
1587             while (++mTestStep < mTestModules.size()) {
1588                 // update the display to show progress
1589                 runOnUiThread(new Runnable() {
1590                     @Override
1591                     public void run() {
1592                         displayTestDevices();
1593                     }
1594                 });
1595 
1596                 // Scan until we find a TestModule that starts playing/recording
1597                 TestModule testModule = mTestModules.get(mTestStep);
1598                 Log.i(TAG, " - testModule: " + testModule.getModuleIndex());
1599 
1600                 // Don't run if it has already been run. This to preserve (possible) error
1601                 // codes from previous runs
1602                 Log.i(TAG, " - hasRun:" + testModule.hasRun(mApi));
1603                 if (!testModule.hasRun(mApi)) {
1604                     int status = startTest(testModule);
1605                     Log.i(TAG, " - status: " + status);
1606                     if (status == TestModule.TESTSTATUS_RUN) {
1607                         // Allow this test to run to completion.
1608                         Log.d(TAG, "Run Test Module:" + testModule.getDescription());
1609                         break;
1610                     }
1611                     Log.d(TAG, "Cancel Test Module:" + testModule.getDescription()
1612                             + " status:" + testModule.getTestStateString(mApi));
1613                     // Otherwise, playing/recording failed, look for the next TestModule
1614                     mDuplexAudioManager.stop();
1615                 }
1616             }
1617 
1618             if (mTestStep >= mTestModules.size()) {
1619                 stopTest();
1620                 completeTest();
1621             }
1622         }
1623 
generateReport(TextFormatter textFormatter)1624         TextFormatter generateReport(TextFormatter textFormatter) {
1625             textFormatter.openHeading(3);
1626             textFormatter.appendText("Test API: ");
1627             textFormatter.appendText(audioApiToString(mApi));
1628             textFormatter.closeHeading(3);
1629 
1630             for (TestModule module : mTestModules) {
1631                 module.generateReport(mApi, textFormatter);
1632             }
1633 
1634             return textFormatter;
1635         }
1636 
1637         //
1638         // CTS VerifierReportLog stuff
1639         //
generateReportLog()1640         void generateReportLog() {
1641             int testIndex = 0;
1642             for (TestModule module : mTestModules) {
1643                 for (int api = TEST_API_NATIVE; api < NUM_TEST_APIS; api++) {
1644                     module.generateReportLog(api);
1645                 }
1646             }
1647         }
1648     }
1649 
1650     /**
1651      * @return short name of the physical route
1652      */
getRouteDescription()1653     abstract String getRouteDescription();
1654 
1655     /**
1656      * Delete all the previously saved WAV files so the user does not
1657      * debug obsolete data.
1658      */
deleteOldWaveFiles()1659     public void deleteOldWaveFiles() {
1660         if (mRecordingDir.exists()) {
1661             File[] files = mRecordingDir.listFiles();
1662             if (files != null) {
1663                 for (File file : files) {
1664                     if (file.isFile()) {
1665                         if (!file.delete()) {
1666                             Log.e(TAG, "Failed to delete file: " + file.getAbsolutePath());
1667                         }
1668                     }
1669                 }
1670             }
1671         }
1672     }
1673 
saveWaveFile(BaseSineAnalyzer mAnalyzer, int index)1674     private String saveWaveFile(BaseSineAnalyzer mAnalyzer, int index) {
1675         File waveFile = new File(mRecordingDir,
1676                 String.format(Locale.US, "paths_%s_%03d.wav",
1677                         getRouteDescription(), index));
1678 
1679         float[] data = mAnalyzer.getRecordedData();
1680         int numSamples = data.length;
1681         if (numSamples > 0) {
1682             try {
1683                 WaveFileWriter writer = new WaveFileWriter(waveFile);
1684                 writer.setFrameRate(mAnalyzer.getSampleRate());
1685                 writer.setBitsPerSample(24);
1686                 writer.write(data);
1687                 writer.close();
1688                 return "Wrote " + numSamples + " samples to " + waveFile.getAbsolutePath();
1689             } catch (IOException e) {
1690                 return "FAILED to save " + waveFile.getAbsolutePath()
1691                         + ", " + e.getMessage();
1692             }
1693         } else {
1694             return "No recorded data!";
1695         }
1696     }
1697 
1698     //
1699     // Process Handling
1700     //
startTest(int api)1701     private void startTest(int api) {
1702         if (mDuplexAudioManager == null) {
1703             mDuplexAudioManager = new DuplexAudioManager(null, null);
1704         }
1705 
1706         enableTestButtons(false, true);
1707         getPassButton().setEnabled(false);
1708         deleteOldWaveFiles();
1709 
1710         mTestManager.startTest(api);
1711     }
1712 
stopTest()1713     private void stopTest() {
1714         mTestManager.stopTest();
1715         mTestManager.displayTestDevices();
1716     }
1717 
calculatePass()1718     protected boolean calculatePass() {
1719         return mTestManager.calculatePass();
1720     }
1721 
hasPeripheralSupport()1722     protected abstract boolean hasPeripheralSupport();
1723 
passBtnEnabled()1724     boolean passBtnEnabled() {
1725         return mTestHasBeenRun || !hasPeripheralSupport();
1726     }
1727 
displayNonHandheldMessage()1728     void displayNonHandheldMessage() {
1729         mTextFormatter.clear();
1730         mTextFormatter.openDocument();
1731         mTextFormatter.openParagraph();
1732         mTextFormatter.appendText(getResources().getString(R.string.audio_exempt_nonhandheld));
1733         mTextFormatter.closeParagraph();
1734 
1735         mTextFormatter.closeDocument();
1736         mTextFormatter.put(mResultsView);
1737         showResultsView();
1738     }
1739 
1740     //
1741     // PassFailButtons Overrides
1742     //
1743     @Override
requiresReportLog()1744     public boolean requiresReportLog() {
1745         return true;
1746     }
1747 
1748     //
1749     // CTS VerifierReportLog stuff
1750     //
1751     @Override
getReportFileName()1752     public String getReportFileName() {
1753         return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME;
1754     }
1755 
1756     @Override
getReportSectionName()1757     public final String getReportSectionName() {
1758         return setTestNameSuffix(sCurrentDisplayMode, SECTION_AUDIO_DATAPATHS);
1759     }
1760 
1761     @Override
recordTestResults()1762     public void recordTestResults() {
1763 // TODO Remove all report logging from this file. This is a quick fix.
1764 // This code generates multiple records in the JSON file.
1765 // That duplication is invalid JSON and causes the database
1766 // ingestion to fail.
1767 //        mTestManager.generateReportLog();
1768     }
1769 
getTimestampString()1770     private static String getTimestampString() {
1771         DateFormat df = DateFormat.getDateTimeInstance();
1772         Date now = Calendar.getInstance().getTime();
1773         return "[" + df.format(now) + "]";
1774     }
1775 
shareResults()1776     private void shareResults() {
1777         if (mTextFormatter != null) {
1778             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
1779             sharingIntent.setType("text/text");
1780 
1781             String subjectText = "CTS Verifier - Results " + getTestCategory()
1782                     + " " + getTimestampString();
1783 
1784             sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subjectText);
1785 
1786             // Regenerate the results in text-only format
1787             PlainTextFormatter formatter = new PlainTextFormatter();
1788             mTestManager.generateResultsText(formatter);
1789             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, formatter.toString());
1790 
1791             // Put up the chooser
1792             startActivity(Intent.createChooser(sharingIntent, "Share using:"));
1793         }
1794     }
1795 
1796     //
1797     // AudioMultiApiActivity Overrides
1798     //
1799     @Override
onApiChange(int api)1800     public void onApiChange(int api) {
1801         stopTest();
1802         mTestManager.mApi = api;
1803         mTestManager.validateTestDevices();
1804         mResultsView.invalidate();
1805         mTestHasBeenRun = false;
1806         getPassButton().setEnabled(passBtnEnabled());
1807 
1808         mTestManager.initializeTests();
1809     }
1810 
1811     //
1812     // View.OnClickHandler
1813     //
1814     @Override
onClick(View view)1815     public void onClick(View view) {
1816         int id = view.getId();
1817         if (id == R.id.audio_datapaths_start) {
1818             startTest(mActiveTestAPI);
1819         } else if (id == R.id.audio_datapaths_cancel) {
1820             mTestCanceledByUser = true;
1821             mTestHasBeenRun = false;
1822             stopTest();
1823             mTestManager.completeTest();
1824         } else if (id == R.id.audio_datapaths_clearresults) {
1825             mTestManager.clearTestState();
1826             mTestManager.displayTestDevices();
1827         } else if (id == R.id.audio_datapaths_shareresults) {
1828             shareResults();
1829         } else if (id == R.id.audio_datapaths_showresults) {
1830             showResultsView();
1831         } else if (id == R.id.audioJavaApiBtn || id == R.id.audioNativeApiBtn) {
1832             super.onClick(view);
1833             mTestCanceledByUser = true;
1834             stopTest();
1835             mTestManager.clearTestState();
1836             showDeviceView();
1837             mTestManager.displayTestDevices();
1838         }
1839     }
1840 
1841     //
1842     // (MegaAudio) AppCallback overrides
1843     //
1844     @Override
onDataReady(float[] audioData, int numFrames)1845     public void onDataReady(float[] audioData, int numFrames) {
1846         TestModule testModule = mTestManager.getActiveTestModule();
1847         if (testModule != null) {
1848             mAnalyzer.analyzeBuffer(audioData, testModule.mInChannelCount, numFrames);
1849             mWaveView.setPCMFloatBuff(audioData, testModule.mInChannelCount, numFrames);
1850         }
1851     }
1852 
1853     //
1854     // AudioDeviceCallback overrides
1855     //
1856     private class AudioDeviceConnectionCallback extends AudioDeviceCallback {
stateChangeHandler()1857         void stateChangeHandler() {
1858             Log.i(TAG, "  stateChangeHandler()");
1859             mTestManager.validateTestDevices();
1860             if (!mIsHandheld) {
1861                 displayNonHandheldMessage();
1862                 getPassButton().setEnabled(true);
1863             } else {
1864                 showDeviceView();
1865                 mTestManager.displayTestDevices();
1866                 if (mTestHasBeenRun) {
1867                     getPassButton().setEnabled(passBtnEnabled());
1868                 }
1869             }
1870         }
1871 
1872         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)1873         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
1874             Log.i(TAG, "onAudioDevicesAdded()");
1875             stateChangeHandler();
1876         }
1877 
1878         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)1879         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
1880             Log.i(TAG, "onAudioDevicesRemoved()");
1881             stateChangeHandler();
1882         }
1883     }
1884 }
1885