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