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