1 /* 2 * Copyright 2017 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.mobileer.oboetester; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageInfo; 23 import android.content.pm.PackageManager; 24 import android.media.AudioAttributes; 25 import android.media.AudioDeviceInfo; 26 import android.media.AudioManager; 27 import android.os.Build; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.util.Log; 32 import android.view.View; 33 import android.view.WindowManager; 34 import android.widget.AdapterView; 35 import android.widget.Button; 36 import android.widget.CheckBox; 37 import android.widget.Spinner; 38 import android.widget.Toast; 39 import androidx.annotation.NonNull; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.util.ArrayList; 44 import java.util.Locale; 45 46 /** 47 * Base class for other Activities. 48 */ 49 abstract class TestAudioActivity extends Activity { 50 public static final String TAG = "OboeTester"; 51 52 protected static final int FADER_PROGRESS_MAX = 1000; 53 private static final int INTENT_TEST_DELAY_MILLIS = 1100; 54 55 public static final int AUDIO_STATE_OPEN = 0; 56 public static final int AUDIO_STATE_STARTED = 1; 57 public static final int AUDIO_STATE_PAUSED = 2; 58 public static final int AUDIO_STATE_FLUSHED = 3; 59 public static final int AUDIO_STATE_STOPPED = 4; 60 public static final int AUDIO_STATE_RELEASED = 5; 61 public static final int AUDIO_STATE_CLOSING = 6; 62 public static final int AUDIO_STATE_CLOSED = 7; 63 64 public static final int COLOR_ACTIVE = 0xFFD0D0A0; 65 public static final int COLOR_IDLE = 0xFFD0D0D0; 66 67 // Pass the activity index to native so it can know how to respond to the start and stop calls. 68 // WARNING - must match definitions in NativeAudioContext.h ActivityType 69 public static final int ACTIVITY_TEST_OUTPUT = 0; 70 public static final int ACTIVITY_TEST_INPUT = 1; 71 public static final int ACTIVITY_TAP_TO_TONE = 2; 72 public static final int ACTIVITY_RECORD_PLAY = 3; 73 public static final int ACTIVITY_ECHO = 4; 74 public static final int ACTIVITY_RT_LATENCY = 5; 75 public static final int ACTIVITY_GLITCHES = 6; 76 public static final int ACTIVITY_TEST_DISCONNECT = 7; 77 public static final int ACTIVITY_DATA_PATHS = 8; 78 public static final int ACTIVITY_DYNAMIC_WORKLOAD = 9; 79 80 private int mAudioState = AUDIO_STATE_CLOSED; 81 82 protected ArrayList<StreamContext> mStreamContexts; 83 private Button mOpenButton; 84 private Button mStartButton; 85 private Button mPauseButton; 86 private Button mFlushButton; 87 private Button mStopButton; 88 private Button mReleaseButton; 89 private Button mCloseButton; 90 private MyStreamSniffer mStreamSniffer; 91 private CheckBox mCallbackReturnStopBox; 92 private Spinner mHangTimeSpinner; 93 94 // Only set in some activities 95 protected CommunicationDeviceView mCommunicationDeviceView; 96 private int mSampleRate; 97 private int mSingleTestIndex = -1; 98 private static boolean mBackgroundEnabled; 99 100 protected Bundle mBundleFromIntent; 101 protected boolean mTestRunningByIntent; 102 protected String mResultFileName; 103 private String mTestResults; 104 private ExternalFileWriter mExternalFileWriter = new ExternalFileWriter(this); 105 getTestName()106 public String getTestName() { 107 return "TestAudio"; 108 } 109 110 public static class StreamContext { 111 StreamConfigurationView configurationView; 112 AudioStreamTester tester; 113 isInput()114 boolean isInput() { 115 return tester.getCurrentAudioStream().isInput(); 116 } 117 } 118 119 // Periodically query the status of the streams. 120 protected class MyStreamSniffer { 121 public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150; 122 public static final int SNIFFER_UPDATE_DELAY_MSEC = 300; 123 124 private Handler mHandler; 125 126 // Display status info for the stream. 127 private Runnable runnableCode = new Runnable() { 128 @Override 129 public void run() { 130 boolean streamClosed = false; 131 boolean gotViews = false; 132 for (StreamContext streamContext : mStreamContexts) { 133 AudioStreamBase.StreamStatus status = streamContext.tester.getCurrentAudioStream().getStreamStatus(); 134 AudioStreamBase.DoubleStatistics latencyStatistics = 135 streamContext.tester.getCurrentAudioStream().getLatencyStatistics(); 136 if (streamContext.configurationView != null) { 137 // Handler runs this on the main UI thread. 138 int framesPerBurst = streamContext.tester.getCurrentAudioStream().getFramesPerBurst(); 139 status.framesPerCallback = getFramesPerCallback(); 140 String msg = ""; 141 msg += "timestamp.latency = " + latencyStatistics.dump() + "\n"; 142 msg += status.dump(framesPerBurst); 143 streamContext.configurationView.setStatusText(msg); 144 updateStreamDisplay(); 145 gotViews = true; 146 } 147 148 streamClosed = streamClosed || (status.state >= 12); 149 } 150 151 if (streamClosed) { 152 onStreamClosed(); 153 } else { 154 // Repeat this runnable code block again. 155 if (gotViews) { 156 mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC); 157 } 158 } 159 } 160 }; 161 startStreamSniffer()162 private void startStreamSniffer() { 163 stopStreamSniffer(); 164 mHandler = new Handler(Looper.getMainLooper()); 165 // Start the initial runnable task by posting through the handler 166 mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC); 167 } 168 stopStreamSniffer()169 private void stopStreamSniffer() { 170 if (mHandler != null) { 171 mHandler.removeCallbacks(runnableCode); 172 } 173 } 174 175 } 176 setBackgroundEnabled(boolean enabled)177 public static void setBackgroundEnabled(boolean enabled) { 178 mBackgroundEnabled = enabled; 179 } 180 isBackgroundEnabled()181 public static boolean isBackgroundEnabled() { 182 return mBackgroundEnabled; 183 } 184 onStreamClosed()185 public void onStreamClosed() { 186 } 187 inflateActivity()188 protected abstract void inflateActivity(); 189 updateStreamDisplay()190 void updateStreamDisplay() { 191 } 192 193 @Override onCreate(Bundle savedInstanceState)194 protected void onCreate(Bundle savedInstanceState) { 195 super.onCreate(savedInstanceState); 196 inflateActivity(); 197 findAudioCommon(); 198 199 mBundleFromIntent = getIntent().getExtras(); 200 } 201 202 @Override onNewIntent(Intent intent)203 public void onNewIntent(Intent intent) { 204 mBundleFromIntent = intent.getExtras(); 205 } 206 isTestConfiguredUsingBundle()207 public boolean isTestConfiguredUsingBundle() { 208 return mBundleFromIntent != null; 209 } 210 hideSettingsViews()211 public void hideSettingsViews() { 212 for (StreamContext streamContext : mStreamContexts) { 213 if (streamContext.configurationView != null) { 214 streamContext.configurationView.hideSettingsView(); 215 } 216 } 217 } 218 getActivityType()219 abstract int getActivityType(); 220 setSingleTestIndex(int testIndex)221 public void setSingleTestIndex(int testIndex) { 222 mSingleTestIndex = testIndex; 223 } 224 getSingleTestIndex()225 public int getSingleTestIndex() { 226 return mSingleTestIndex; 227 } 228 229 @Override onStart()230 protected void onStart() { 231 super.onStart(); 232 resetConfiguration(); 233 setActivityType(getActivityType()); 234 // TODO Use LifeCycleObserver instead of this. 235 if (mCommunicationDeviceView != null) { 236 mCommunicationDeviceView.onStart(); 237 } 238 } 239 resetConfiguration()240 protected void resetConfiguration() { 241 } 242 243 @Override onResume()244 public void onResume() { 245 super.onResume(); 246 if (mBundleFromIntent != null) { 247 processBundleFromIntent(); 248 } 249 } 250 setVolumeFromIntent()251 private void setVolumeFromIntent() { 252 float normalizedVolume = IntentBasedTestSupport.getNormalizedVolumeFromBundle(mBundleFromIntent); 253 if (normalizedVolume >= 0.0) { 254 int streamType = IntentBasedTestSupport.getVolumeStreamTypeFromBundle(mBundleFromIntent); 255 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 256 int maxVolume = audioManager.getStreamMaxVolume(streamType); 257 int requestedVolume = (int) (maxVolume * normalizedVolume); 258 audioManager.setStreamVolume(streamType, requestedVolume, 0); 259 } 260 } 261 processBundleFromIntent()262 private void processBundleFromIntent() { 263 if (mTestRunningByIntent) { 264 return; 265 } 266 267 // Delay the test start to avoid race conditions. See Oboe Issue #1533 268 mTestRunningByIntent = true; 269 Handler handler = new Handler(Looper.getMainLooper()); // UI thread 270 handler.postDelayed(new DelayedTestByIntentRunnable(), 271 INTENT_TEST_DELAY_MILLIS); // Delay long enough to get past the onStop() call! 272 273 } 274 275 private class DelayedTestByIntentRunnable implements Runnable { 276 @Override run()277 public void run() { 278 try { 279 mResultFileName = mBundleFromIntent.getString(IntentBasedTestSupport.KEY_FILE_NAME); 280 setVolumeFromIntent(); 281 startTestUsingBundle(); 282 } catch( Exception e) { 283 showErrorToast(e.getMessage()); 284 } 285 } 286 } 287 startTestUsingBundle()288 public void startTestUsingBundle() { 289 } 290 291 @Override onPause()292 protected void onPause() { 293 super.onPause(); 294 } 295 296 @Override onStop()297 protected void onStop() { 298 if (!isBackgroundEnabled()) { 299 Log.i(TAG, "onStop() called so stop the test ========================="); 300 onStopTest(); 301 } 302 if (mCommunicationDeviceView != null) { 303 mCommunicationDeviceView.onStop(); 304 } 305 super.onStop(); 306 } 307 308 @Override onDestroy()309 protected void onDestroy() { 310 if (isBackgroundEnabled()) { 311 Log.i(TAG, "onDestroy() called so stop the test ========================="); 312 onStopTest(); 313 } 314 mAudioState = AUDIO_STATE_CLOSED; 315 super.onDestroy(); 316 } 317 updateEnabledWidgets()318 protected void updateEnabledWidgets() { 319 if (mOpenButton != null) { 320 mOpenButton.setBackgroundColor(mAudioState == AUDIO_STATE_OPEN ? COLOR_ACTIVE : COLOR_IDLE); 321 mStartButton.setBackgroundColor(mAudioState == AUDIO_STATE_STARTED ? COLOR_ACTIVE : COLOR_IDLE); 322 mPauseButton.setBackgroundColor(mAudioState == AUDIO_STATE_PAUSED ? COLOR_ACTIVE : COLOR_IDLE); 323 mFlushButton.setBackgroundColor(mAudioState == AUDIO_STATE_FLUSHED ? COLOR_ACTIVE : COLOR_IDLE); 324 mStopButton.setBackgroundColor(mAudioState == AUDIO_STATE_STOPPED ? COLOR_ACTIVE : COLOR_IDLE); 325 mReleaseButton.setBackgroundColor(mAudioState == AUDIO_STATE_RELEASED ? COLOR_ACTIVE : COLOR_IDLE); 326 mCloseButton.setBackgroundColor(mAudioState == AUDIO_STATE_CLOSED ? COLOR_ACTIVE : COLOR_IDLE); 327 } 328 setConfigViewsEnabled(mAudioState == AUDIO_STATE_CLOSED); 329 } 330 setConfigViewsEnabled(boolean b)331 private void setConfigViewsEnabled(boolean b) { 332 for (StreamContext streamContext : mStreamContexts) { 333 if (streamContext.configurationView != null) { 334 streamContext.configurationView.setChildrenEnabled(b); 335 } 336 } 337 } 338 applyConfigurationViewsToModels()339 private void applyConfigurationViewsToModels() { 340 for (StreamContext streamContext : mStreamContexts) { 341 if (streamContext.configurationView != null) { 342 streamContext.configurationView.applyToModel(streamContext.tester.requestedConfiguration); 343 } 344 } 345 } 346 isOutput()347 abstract boolean isOutput(); 348 clearStreamContexts()349 public void clearStreamContexts() { 350 mStreamContexts.clear(); 351 } 352 addOutputStreamContext()353 public StreamContext addOutputStreamContext() { 354 StreamContext streamContext = new StreamContext(); 355 streamContext.tester = AudioOutputTester.getInstance(); 356 streamContext.configurationView = (StreamConfigurationView) 357 findViewById(R.id.outputStreamConfiguration); 358 if (streamContext.configurationView == null) { 359 streamContext.configurationView = (StreamConfigurationView) 360 findViewById(R.id.streamConfiguration); 361 } 362 if (streamContext.configurationView != null) { 363 streamContext.configurationView.setOutput(true); 364 } 365 mStreamContexts.add(streamContext); 366 return streamContext; 367 } 368 addAudioOutputTester()369 public AudioOutputTester addAudioOutputTester() { 370 StreamContext streamContext = addOutputStreamContext(); 371 return (AudioOutputTester) streamContext.tester; 372 } 373 addInputStreamContext()374 public StreamContext addInputStreamContext() { 375 StreamContext streamContext = new StreamContext(); 376 streamContext.tester = AudioInputTester.getInstance(); 377 streamContext.configurationView = (StreamConfigurationView) 378 findViewById(R.id.inputStreamConfiguration); 379 if (streamContext.configurationView == null) { 380 streamContext.configurationView = (StreamConfigurationView) 381 findViewById(R.id.streamConfiguration); 382 } 383 if (streamContext.configurationView != null) { 384 streamContext.configurationView.setOutput(false); 385 } 386 mStreamContexts.add(streamContext); 387 return streamContext; 388 } 389 addAudioInputTester()390 public AudioInputTester addAudioInputTester() { 391 StreamContext streamContext = addInputStreamContext(); 392 return (AudioInputTester) streamContext.tester; 393 } 394 updateStreamConfigurationViews()395 void updateStreamConfigurationViews() { 396 for (StreamContext streamContext : mStreamContexts) { 397 if (streamContext.configurationView != null) { 398 streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration); 399 } 400 } 401 } 402 getFirstInputStreamContext()403 StreamContext getFirstInputStreamContext() { 404 for (StreamContext streamContext : mStreamContexts) { 405 if (streamContext.isInput()) 406 return streamContext; 407 } 408 return null; 409 } 410 getFirstOutputStreamContext()411 StreamContext getFirstOutputStreamContext() { 412 for (StreamContext streamContext : mStreamContexts) { 413 if (!streamContext.isInput()) 414 return streamContext; 415 } 416 return null; 417 } 418 findAudioCommon()419 protected void findAudioCommon() { 420 mOpenButton = (Button) findViewById(R.id.button_open); 421 if (mOpenButton != null) { 422 mStartButton = (Button) findViewById(R.id.button_start); 423 mPauseButton = (Button) findViewById(R.id.button_pause); 424 mFlushButton = (Button) findViewById(R.id.button_flush); 425 mStopButton = (Button) findViewById(R.id.button_stop); 426 mReleaseButton = (Button) findViewById(R.id.button_release); 427 mCloseButton = (Button) findViewById(R.id.button_close); 428 } 429 mStreamContexts = new ArrayList<StreamContext>(); 430 431 mCallbackReturnStopBox = (CheckBox) findViewById(R.id.callbackReturnStop); 432 if (mCallbackReturnStopBox != null) { 433 mCallbackReturnStopBox.setOnClickListener(new View.OnClickListener() { 434 @Override 435 public void onClick(View v) { 436 OboeAudioStream.setCallbackReturnStop(mCallbackReturnStopBox.isChecked()); 437 } 438 }); 439 } 440 OboeAudioStream.setCallbackReturnStop(false); 441 442 mHangTimeSpinner = (Spinner) findViewById(R.id.spinner_hang_time); 443 if (mHangTimeSpinner != null) { 444 mHangTimeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 445 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 446 String hangTimeText = (String) mHangTimeSpinner.getAdapter().getItem(position); 447 int hangTimeMillis = Integer.parseInt(hangTimeText); 448 Log.d(TAG, "Hang Time = " + hangTimeMillis + " msec"); 449 450 OboeAudioStream.setHangTimeMillis(hangTimeMillis); 451 } 452 453 public void onNothingSelected(AdapterView<?> parent) { 454 OboeAudioStream.setHangTimeMillis(0); 455 } 456 }); 457 } 458 OboeAudioStream.setHangTimeMillis(0); 459 460 mStreamSniffer = new MyStreamSniffer(); 461 } 462 updateNativeAudioParameters()463 private void updateNativeAudioParameters() { 464 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 465 AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 466 String text = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 467 int audioManagerSampleRate = Integer.parseInt(text); 468 text = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 469 int audioManagerFramesPerBurst = Integer.parseInt(text); 470 setDefaultAudioValues(audioManagerSampleRate, audioManagerFramesPerBurst); 471 } 472 } 473 showErrorToast(String message)474 protected void showErrorToast(String message) { 475 Log.e(TAG, "showErrorToast(\"" + message + "\")"); 476 String text = "Error: " + message; 477 showToast(text); 478 } 479 showToast(final String message)480 protected void showToast(final String message) { 481 runOnUiThread(new Runnable() { 482 @Override 483 public void run() { 484 Toast.makeText(TestAudioActivity.this, 485 message, 486 Toast.LENGTH_SHORT).show(); 487 } 488 }); 489 } 490 onStartAllContexts()491 private void onStartAllContexts() { 492 for (StreamContext streamContext : mStreamContexts) { 493 streamContext.tester.getCurrentAudioStream().onStart(); 494 } 495 } onStopAllContexts()496 private void onStopAllContexts() { 497 for (StreamContext streamContext : mStreamContexts) { 498 streamContext.tester.getCurrentAudioStream().onStop(); 499 } 500 } 501 openAudio(View view)502 public void openAudio(View view) { 503 try { 504 openAudio(); 505 } catch (Exception e) { 506 showErrorToast("openAudio() caught " + e.getMessage()); 507 } 508 } 509 clearHangTime()510 void clearHangTime() { 511 OboeAudioStream.setHangTimeMillis(0); 512 if (mHangTimeSpinner != null) { 513 mHangTimeSpinner.setSelection(0); 514 } 515 } 516 startAudio(View view)517 public void startAudio(View view) { 518 Log.i(TAG, "startAudio() called ======================================="); 519 clearHangTime(); // start running 520 try { 521 startAudio(); 522 } catch (Exception e) { 523 showErrorToast("startAudio() caught " + e.getMessage()); 524 } 525 keepScreenOn(true); 526 } 527 keepScreenOn(boolean on)528 protected void keepScreenOn(boolean on) { 529 if (on) { 530 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 531 } else { 532 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 533 } 534 } 535 stopAudio(View view)536 public void stopAudio(View view) { 537 stopAudio(); 538 keepScreenOn(false); 539 } 540 pauseAudio(View view)541 public void pauseAudio(View view) { 542 pauseAudio(); 543 keepScreenOn(false); 544 } 545 flushAudio(View view)546 public void flushAudio(View view) { 547 flushAudio(); 548 } 549 closeAudio(View view)550 public void closeAudio(View view) { 551 closeAudio(); 552 } 553 releaseAudio(View view)554 public void releaseAudio(View view) { 555 releaseAudio(); 556 } 557 getSampleRate()558 public int getSampleRate() { 559 return mSampleRate; 560 } 561 openAudio()562 public void openAudio() throws IOException { 563 closeAudio(); 564 565 updateNativeAudioParameters(); 566 567 if (!isTestConfiguredUsingBundle()) { 568 applyConfigurationViewsToModels(); 569 } 570 571 int sampleRate = 0; // Use the OUTPUT sample rate for INPUT 572 573 // Open output streams then open input streams. 574 // This is so that the capacity of input stream can be expanded to 575 // match the burst size of the output for full duplex. 576 for (StreamContext streamContext : mStreamContexts) { 577 if (!streamContext.isInput()) { // OUTPUT? 578 openStreamContext(streamContext); 579 int streamSampleRate = streamContext.tester.actualConfiguration.getSampleRate(); 580 if (sampleRate == 0) { 581 sampleRate = streamSampleRate; 582 } 583 584 if (shouldSetStreamControlByAttributes()) { 585 // Associate volume keys with this output stream. 586 int actualUsage = streamContext.tester.actualConfiguration.getUsage(); 587 int actualContentType = streamContext.tester.actualConfiguration.getContentType(); 588 setStreamControlByAttributes(actualUsage, actualContentType); 589 } 590 } 591 } 592 for (StreamContext streamContext : mStreamContexts) { 593 if (streamContext.isInput()) { 594 if (sampleRate != 0) { 595 streamContext.tester.requestedConfiguration.setSampleRate(sampleRate); 596 } 597 openStreamContext(streamContext); 598 } 599 } 600 updateEnabledWidgets(); 601 onStartAllContexts(); 602 mStreamSniffer.startStreamSniffer(); 603 } 604 shouldSetStreamControlByAttributes()605 protected boolean shouldSetStreamControlByAttributes() { 606 return true; 607 } 608 609 /** 610 * Associate the volume keys with the stream we are playing. 611 * @param usage usage for the stream 612 * @param contentType tupe of the stream 613 */ setStreamControlByAttributes(int usage, int contentType)614 private void setStreamControlByAttributes(int usage, int contentType) { 615 AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage) 616 .setContentType(contentType).build(); 617 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 618 int volumeControlStream = attributes.getVolumeControlStream(); 619 Log.i(TAG, "setVolumeControlStream(" + volumeControlStream + ")"); 620 setVolumeControlStream(volumeControlStream); 621 } 622 } 623 624 /** 625 * @param deviceId 626 * @return true if the device is TYPE_BLUETOOTH_SCO 627 */ isScoDevice(int deviceId)628 boolean isScoDevice(int deviceId) { 629 if (deviceId == 0) return false; // Unspecified 630 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 631 final AudioDeviceInfo[] devices = audioManager.getDevices( 632 AudioManager.GET_DEVICES_INPUTS | AudioManager.GET_DEVICES_OUTPUTS); 633 for (AudioDeviceInfo device : devices) { 634 if (device.getId() == deviceId) { 635 return device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO; 636 } 637 } 638 return false; 639 } 640 openStreamContext(StreamContext streamContext)641 private void openStreamContext(StreamContext streamContext) throws IOException { 642 StreamConfiguration requestedConfig = streamContext.tester.requestedConfiguration; 643 StreamConfiguration actualConfig = streamContext.tester.actualConfiguration; 644 645 streamContext.tester.open(); // OPEN the stream 646 647 mSampleRate = actualConfig.getSampleRate(); 648 mAudioState = AUDIO_STATE_OPEN; 649 int sessionId = actualConfig.getSessionId(); 650 if (streamContext.configurationView != null) { 651 if (sessionId > 0) { 652 try { 653 streamContext.configurationView.setupEffects(sessionId); 654 } catch (Exception e) { 655 showErrorToast("openStreamContext() caught " + e.getMessage()); 656 } 657 } 658 streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration); 659 } 660 } 661 662 // Native methods startNative()663 private native int startNative(); 664 pauseNative()665 private native int pauseNative(); 666 flushNative()667 private native int flushNative(); 668 stopNative()669 private native int stopNative(); 670 releaseNative()671 private native int releaseNative(); 672 setActivityType(int activityType)673 protected native void setActivityType(int activityType); 674 getFramesPerCallback()675 private native int getFramesPerCallback(); 676 setUseAlternativeAdpf(boolean enabled)677 public native void setUseAlternativeAdpf(boolean enabled); 678 setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst)679 private static native void setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst); 680 startAudio()681 public void startAudio() throws IOException { 682 Log.i(TAG, "startAudio() called ========================="); 683 int result = startNative(); 684 if (result != 0) { 685 showErrorToast("Start failed with " + result + ", " + StreamConfiguration.convertErrorToText(result)); 686 throw new IOException("startNative returned " + result + ", " + StreamConfiguration.convertErrorToText(result)); 687 } else { 688 onStartAllContexts(); 689 for (StreamContext streamContext : mStreamContexts) { 690 StreamConfigurationView configView = streamContext.configurationView; 691 if (configView != null) { 692 configView.updateDisplay(streamContext.tester.actualConfiguration); 693 } 694 } 695 mAudioState = AUDIO_STATE_STARTED; 696 updateEnabledWidgets(); 697 } 698 } 699 toastPauseError(int result)700 protected void toastPauseError(int result) { 701 showErrorToast("Pause failed with " + result + ", " + StreamConfiguration.convertErrorToText(result)); 702 } 703 pauseAudio()704 public void pauseAudio() { 705 int result = pauseNative(); 706 if (result != 0) { 707 toastPauseError(result); 708 } else { 709 mAudioState = AUDIO_STATE_PAUSED; 710 updateEnabledWidgets(); 711 onStopAllContexts(); 712 } 713 } 714 flushAudio()715 public void flushAudio() { 716 int result = flushNative(); 717 if (result != 0) { 718 showErrorToast("Flush failed with " + result + ", " + StreamConfiguration.convertErrorToText(result)); 719 } else { 720 mAudioState = AUDIO_STATE_FLUSHED; 721 updateEnabledWidgets(); 722 } 723 } 724 stopAudio()725 public void stopAudio() { 726 int result = stopNative(); 727 if (result != 0) { 728 showErrorToast("Stop failed with " + result + ", " + StreamConfiguration.convertErrorToText(result)); 729 } else { 730 mAudioState = AUDIO_STATE_STOPPED; 731 updateEnabledWidgets(); 732 onStopAllContexts(); 733 } 734 } 735 releaseAudio()736 public void releaseAudio() { 737 int result = releaseNative(); 738 if (result != 0) { 739 showErrorToast("Release failed with " + result + ", " + StreamConfiguration.convertErrorToText(result)); 740 } else { 741 mAudioState = AUDIO_STATE_RELEASED; 742 updateEnabledWidgets(); 743 onStopAllContexts(); 744 } 745 } 746 runTest()747 public void runTest() { 748 } 749 saveIntentLog()750 public void saveIntentLog() { 751 } 752 753 // This should only be called from UI events such as onStop or a button press. onStopTest()754 public void onStopTest() { 755 stopTest(); 756 } 757 stopTest()758 public void stopTest() { 759 stopAudio(); 760 closeAudio(); 761 } 762 stopAudioQuiet()763 public void stopAudioQuiet() { 764 stopNative(); 765 mAudioState = AUDIO_STATE_STOPPED; 766 updateEnabledWidgets(); 767 } 768 769 // Make synchronized so we don't close from two streams at the same time. closeAudio()770 public synchronized void closeAudio() { 771 if (mAudioState >= AUDIO_STATE_CLOSING) { 772 Log.d(TAG, "closeAudio() already closing"); 773 return; 774 } 775 mAudioState = AUDIO_STATE_CLOSING; 776 777 mStreamSniffer.stopStreamSniffer(); 778 // Close output streams first because legacy callbacks may still be active 779 // and an output stream may be calling the input stream. 780 for (StreamContext streamContext : mStreamContexts) { 781 if (!streamContext.isInput()) { 782 streamContext.tester.close(); 783 } 784 } 785 for (StreamContext streamContext : mStreamContexts) { 786 if (streamContext.isInput()) { 787 streamContext.tester.close(); 788 } 789 } 790 791 mAudioState = AUDIO_STATE_CLOSED; 792 updateEnabledWidgets(); 793 } 794 startBluetoothSco()795 void startBluetoothSco() { 796 AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 797 myAudioMgr.startBluetoothSco(); 798 } 799 stopBluetoothSco()800 void stopBluetoothSco() { 801 AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 802 myAudioMgr.stopBluetoothSco(); 803 } 804 805 @NonNull getCommonTestReport()806 protected String getCommonTestReport() { 807 StringBuffer report = new StringBuffer(); 808 // Add some extra information for the remote tester. 809 report.append("build.fingerprint = " + Build.FINGERPRINT + "\n"); 810 try { 811 PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); 812 report.append(String.format(Locale.getDefault(), "test.version = %s\n", pinfo.versionName)); 813 report.append(String.format(Locale.getDefault(), "test.version.code = %d\n", pinfo.versionCode)); 814 } catch (PackageManager.NameNotFoundException e) { 815 } 816 report.append("time.millis = " + System.currentTimeMillis() + "\n"); 817 818 if (mStreamContexts.size() == 0) { 819 report.append("ERROR: no active streams" + "\n"); 820 } else { 821 StreamContext streamContext = mStreamContexts.get(0); 822 AudioStreamTester streamTester = streamContext.tester; 823 report.append(streamTester.actualConfiguration.dump()); 824 AudioStreamBase.StreamStatus status = streamTester.getCurrentAudioStream().getStreamStatus(); 825 AudioStreamBase.DoubleStatistics latencyStatistics = 826 streamTester.getCurrentAudioStream().getLatencyStatistics(); 827 int framesPerBurst = streamTester.getCurrentAudioStream().getFramesPerBurst(); 828 status.framesPerCallback = getFramesPerCallback(); 829 report.append("timestamp.latency = " + latencyStatistics.dump() + "\n"); 830 report.append(status.dump(framesPerBurst)); 831 } 832 833 return report.toString(); 834 } 835 maybeWriteTestResult(String resultString)836 File maybeWriteTestResult(String resultString) { 837 File fileWritten = null; 838 if (mResultFileName != null) { 839 try { 840 fileWritten = mExternalFileWriter.writeStringToExternalFile(resultString, mResultFileName); 841 } catch (IOException e) { 842 e.printStackTrace(); 843 showErrorToast(" writing result file. " + e.getMessage()); 844 } 845 mResultFileName = null; 846 } 847 return fileWritten; 848 } 849 } 850