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