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