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