1 /* 2 * Copyright (C) 2014 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 org.drrickorang.loopback; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.app.DialogFragment; 22 import android.app.Fragment; 23 import android.app.FragmentManager; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.pm.PackageManager; 29 import android.database.Cursor; 30 import android.graphics.Bitmap; 31 import android.graphics.Canvas; 32 import android.hardware.usb.UsbDevice; 33 import android.hardware.usb.UsbManager; 34 import android.media.AudioDeviceInfo; 35 import android.media.AudioManager; 36 import android.net.Uri; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.IBinder; 41 import android.os.Looper; 42 import android.os.Message; 43 import android.os.ParcelFileDescriptor; 44 import android.provider.MediaStore; 45 import androidx.core.app.ActivityCompat; 46 import androidx.core.content.ContextCompat; 47 import android.text.format.DateFormat; 48 import android.util.Log; 49 import android.view.Gravity; 50 import android.view.Menu; 51 import android.view.MenuInflater; 52 import android.view.MenuItem; 53 import android.view.View; 54 import android.view.ViewGroup; 55 import android.widget.Button; 56 import android.widget.LinearLayout; 57 import android.widget.PopupWindow; 58 import android.widget.SeekBar; 59 import android.widget.TextView; 60 import android.widget.Toast; 61 62 import java.io.File; 63 import java.io.FileDescriptor; 64 import java.io.FileOutputStream; 65 import java.util.Arrays; 66 import java.util.HashMap; 67 import java.util.Locale; 68 import java.util.Map; 69 70 71 /** 72 * This is the main activity of the Loopback app. Two tests (latency test and buffer test) can be 73 * initiated here. Note: buffer test and glitch detection is the same test, it's just that this test 74 * has two parts of result. 75 */ 76 77 public class LoopbackActivity extends Activity 78 implements SaveFilesDialogFragment.NoticeDialogListener { 79 private static final String TAG = "LoopbackActivity"; 80 81 private static final int SAVE_TO_WAVE_REQUEST = 42; 82 private static final int SAVE_TO_PNG_REQUEST = 43; 83 private static final int SAVE_TO_TXT_REQUEST = 44; 84 private static final int SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST = 45; 85 private static final int SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST = 46; 86 private static final int SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST = 47; 87 private static final int SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST = 48; 88 private static final int SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST = 49; 89 private static final int SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST = 50; 90 private static final int SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST = 51; 91 private static final int SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST = 52; 92 93 private static final int SETTINGS_ACTIVITY_REQUEST = 54; 94 95 private static final int THREAD_SLEEP_DURATION_MS = 200; 96 private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY = 201; 97 private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER = 202; 98 private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS = 203; 99 private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT = 204; 100 private static final int LATENCY_TEST_STARTED = 300; 101 private static final int LATENCY_TEST_ENDED = 301; 102 private static final int BUFFER_TEST_STARTED = 302; 103 private static final int BUFFER_TEST_ENDED = 303; 104 private static final int CALIBRATION_STARTED = 304; 105 private static final int CALIBRATION_ENDED = 305; 106 private static final int NO_TEST_ACTIVE = 306; 107 108 // 0-100 controls compression rate, currently ignore because PNG format is being used 109 private static final int EXPORTED_IMAGE_QUALITY = 100; 110 111 private static final int HISTOGRAM_EXPORT_WIDTH = 2000; 112 private static final int HISTOGRAM_EXPORT_HEIGHT = 2000; 113 private static final int HEATMAP_DRAW_WIDTH = 2560; 114 private static final int HEATMAP_DRAW_HEIGHT = 1440; 115 private static final int HEATMAP_EXPORT_DIVISOR = 2; 116 117 LoopbackAudioThread mAudioThread = null; 118 NativeAudioThread mNativeAudioThread = null; 119 private Thread mCalibrationThread; 120 private WavePlotView mWavePlotView; 121 private String mTestStartTimeString = "IncorrectTime"; // The time the test begins 122 private static final String FILE_SAVE_PATH = "file://mnt/sdcard/"; 123 124 private SeekBar mBarMasterLevel; // drag the volume 125 private TextView mTextInfo; 126 private TextView mTextViewCurrentLevel; 127 private TextView mTextViewResultSummary; 128 129 private static final String TAG_RETAINED_FRAGMENT = "RetainedFragment"; 130 private RetainedFragment mRetainedFragment; 131 private int mTestType; 132 private Correlation mCorrelation = new Correlation(); 133 private BufferPeriod mRecorderBufferPeriod = new BufferPeriod(); 134 private BufferPeriod mPlayerBufferPeriod = new BufferPeriod(); 135 136 // for native buffer period 137 private int[] mNativeRecorderBufferPeriodArray; 138 private int mNativeRecorderMaxBufferPeriod; 139 private double mNativeRecorderStdDevBufferPeriod; 140 private int[] mNativePlayerBufferPeriodArray; 141 private int mNativePlayerMaxBufferPeriod; 142 private double mNativePlayerStdDevBufferPeriod; 143 private BufferCallbackTimes mRecorderCallbackTimes; 144 private BufferCallbackTimes mPlayerCallbackTimes; 145 146 private static final String INTENT_SAMPLING_FREQUENCY = "SF"; 147 private static final String INTENT_CHANNEL_INDEX = "CI"; 148 private static final String INTENT_CORRELATION_BLOCK_SIZE = "BS"; 149 private static final String INTENT_FILENAME = "FileName"; 150 private static final String INTENT_RECORDER_BUFFER = "RecorderBuffer"; 151 private static final String INTENT_PLAYER_BUFFER = "PlayerBuffer"; 152 private static final String INTENT_AUDIO_THREAD = "AudioThread"; 153 private static final String INTENT_MIC_SOURCE = "MicSource"; 154 private static final String INTENT_PERFORMANCE_MODE = "PerformanceMode"; 155 private static final String INTENT_AUDIO_LEVEL = "AudioLevel"; 156 private static final String INTENT_IGNORE_FIRST_FRAMES = "IgnoreFirstFrames"; 157 private static final String INTENT_TEST_TYPE = "TestType"; 158 private static final String INTENT_BUFFER_TEST_DURATION = "BufferTestDuration"; 159 private static final String INTENT_NUMBER_LOAD_THREADS = "NumLoadThreads"; 160 private static final String INTENT_ENABLE_SYSTRACE = "CaptureSysTrace"; 161 private static final String INTENT_ENABLE_WAVCAPTURE = "CaptureWavs"; 162 private static final String INTENT_NUM_CAPTURES = "NumCaptures"; 163 private static final String INTENT_WAV_DURATION = "WavDuration"; 164 private static final String INTENT_USB_AUDIO_ROUTE = "USB"; 165 166 // for running the test using adb command 167 private volatile boolean mIntentRunning; // if it is running triggered by intent with parameters 168 private String mIntentFileName; 169 170 // Note: these values should only be assigned in restartAudioSystem() 171 private int mAudioThreadType = Constant.UNKNOWN; 172 private int mMicSource; 173 private int mPerformanceMode; 174 private int mSamplingRate; 175 private int mChannelIndex; 176 private int mSoundLevel; 177 private int mPlayerBufferSizeInBytes; 178 private int mRecorderBufferSizeInBytes; 179 private int mIgnoreFirstFrames; // TODO: this only applies to native mode 180 private CaptureHolder mCaptureHolder; 181 182 // for buffer test 183 private int[] mGlitchesData; 184 private boolean mGlitchingIntervalTooLong; 185 private int mFFTSamplingSize; 186 private int mFFTOverlapSamples; 187 private long mBufferTestStartTime; 188 private int mBufferTestElapsedSeconds; 189 private int mBufferTestDurationInSeconds; 190 private int mBufferTestWavePlotDurationInSeconds; 191 192 // CTS Test Flag 193 private boolean mIsCTSTest; 194 private int mCtsNumIterations; 195 196 // threads that load CPUs 197 private LoadThread[] mLoadThreads; 198 199 // for getting the Service 200 boolean mBound = false; 201 private AudioTestService mAudioTestService; 202 private final ServiceConnection mServiceConnection = new ServiceConnection() { 203 public void onServiceConnected(ComponentName className, IBinder service) { 204 mAudioTestService = ((AudioTestService.AudioTestBinder) service).getService(); 205 mBound = true; 206 } 207 208 public void onServiceDisconnected(ComponentName className) { 209 mAudioTestService = null; 210 mBound = false; 211 } 212 }; 213 214 private Handler mMessageHandler = new Handler(Looper.getMainLooper()) { 215 public void handleMessage(Message msg) { 216 super.handleMessage(msg); 217 switch (msg.what) { 218 219 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 220 log("got message java latency test started!!"); 221 showToast("Java Latency Test Started"); 222 resetResults(); 223 refreshState(); 224 refreshPlots(); 225 break; 226 227 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 228 log("got message java latency test rec can't start!!"); 229 showToastImportant("Java Latency Test Recording Error. Please try again"); 230 refreshState(); 231 stopAudioTestThreads(); 232 mIntentRunning = false; 233 refreshSoundLevelBar(); 234 break; 235 236 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 237 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 238 if (mAudioThread != null) { 239 mRetainedFragment.setWaveData(mAudioThread.getWaveData()); 240 mRecorderCallbackTimes = mRecorderBufferPeriod.getCallbackTimes(); 241 mPlayerCallbackTimes = mPlayerBufferPeriod.getCallbackTimes(); 242 mCorrelation.computeCorrelation(mRetainedFragment.getWaveData(), mSamplingRate); 243 log("got message java latency rec complete!!"); 244 refreshPlots(); 245 refreshState(); 246 247 switch (msg.what) { 248 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 249 showToast("Java Latency Test Stopped"); 250 break; 251 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 252 showToast("Java Latency Test Completed"); 253 break; 254 } 255 256 stopAudioTestThreads(); 257 if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) { 258 saveAllTo(mIntentFileName); 259 } 260 mIntentRunning = false; 261 } 262 refreshSoundLevelBar(); 263 break; 264 265 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 266 log("got message java buffer test rec started!!"); 267 showToast("Java Buffer Test Started"); 268 resetResults(); 269 refreshState(); 270 refreshPlots(); 271 mBufferTestStartTime = System.currentTimeMillis(); 272 break; 273 274 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 275 log("got message java buffer test rec can't start!!"); 276 showToastImportant("Java Buffer Test Recording Error. Please try again"); 277 refreshState(); 278 stopAudioTestThreads(); 279 mIntentRunning = false; 280 refreshSoundLevelBar(); 281 break; 282 283 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 284 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 285 if (mAudioThread != null) { 286 mRetainedFragment.setWaveData(mAudioThread.getWaveData()); 287 mGlitchesData = mAudioThread.getAllGlitches(); 288 mGlitchingIntervalTooLong = mAudioThread.getGlitchingIntervalTooLong(); 289 mFFTSamplingSize = mAudioThread.getFFTSamplingSize(); 290 mFFTOverlapSamples = mAudioThread.getFFTOverlapSamples(); 291 mRecorderCallbackTimes = mRecorderBufferPeriod.getCallbackTimes(); 292 mPlayerCallbackTimes = mPlayerBufferPeriod.getCallbackTimes(); 293 refreshPlots(); // only plot that last few seconds 294 refreshState(); 295 //rounded up number of seconds elapsed 296 mBufferTestElapsedSeconds = 297 (int) ((System.currentTimeMillis() - mBufferTestStartTime + 298 Constant.MILLIS_PER_SECOND - 1) / Constant.MILLIS_PER_SECOND); 299 switch (msg.what) { 300 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 301 showToast("Java Buffer Test Stopped"); 302 break; 303 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 304 showToast("Java Buffer Test Completed"); 305 break; 306 } 307 if (getApp().isCaptureEnabled()) { 308 mCaptureHolder.stopLoopbackListenerScript(); 309 } 310 stopAudioTestThreads(); 311 if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) { 312 saveAllTo(mIntentFileName); 313 } 314 mIntentRunning = false; 315 } 316 refreshSoundLevelBar(); 317 break; 318 319 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 320 showToast("Native Latency Test Started"); 321 log("got message native latency test rec started!!"); 322 resetResults(); 323 refreshState(); 324 refreshPlots(); 325 break; 326 327 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 328 log("got message native buffer test rec started!!"); 329 showToast("Native Buffer Test Started"); 330 resetResults(); 331 refreshState(); 332 refreshPlots(); 333 mBufferTestStartTime = System.currentTimeMillis(); 334 break; 335 336 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 337 log("got message native latency test rec can't start!!"); 338 showToastImportant("Native Latency Test Recording Error. Please try again"); 339 refreshState(); 340 mIntentRunning = false; 341 refreshSoundLevelBar(); 342 break; 343 344 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 345 log("got message native buffer test rec can't start!!"); 346 showToastImportant("Native Buffer Test Recording Error. Please try again"); 347 refreshState(); 348 mIntentRunning = false; 349 refreshSoundLevelBar(); 350 break; 351 352 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 353 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 354 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 355 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 356 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS: 357 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS: 358 if (mNativeAudioThread != null) { 359 mGlitchesData = mNativeAudioThread.getNativeAllGlitches(); 360 mGlitchingIntervalTooLong = mNativeAudioThread.getGlitchingIntervalTooLong(); 361 mFFTSamplingSize = mNativeAudioThread.getNativeFFTSamplingSize(); 362 mFFTOverlapSamples = mNativeAudioThread.getNativeFFTOverlapSamples(); 363 mRetainedFragment.setWaveData(mNativeAudioThread.getWaveData()); 364 mNativeRecorderBufferPeriodArray = mNativeAudioThread.getRecorderBufferPeriod(); 365 mNativeRecorderMaxBufferPeriod = 366 mNativeAudioThread.getRecorderMaxBufferPeriod(); 367 mNativeRecorderStdDevBufferPeriod = 368 mNativeAudioThread.getRecorderStdDevBufferPeriod(); 369 mNativePlayerBufferPeriodArray = mNativeAudioThread.getPlayerBufferPeriod(); 370 mNativePlayerMaxBufferPeriod = mNativeAudioThread.getPlayerMaxBufferPeriod(); 371 mNativePlayerStdDevBufferPeriod = 372 mNativeAudioThread.getPlayerStdDevBufferPeriod(); 373 mRecorderCallbackTimes = mNativeAudioThread.getRecorderCallbackTimes(); 374 mPlayerCallbackTimes = mNativeAudioThread.getPlayerCallbackTimes(); 375 376 if (msg.what != NativeAudioThread. 377 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE) { 378 mCorrelation.computeCorrelation(mRetainedFragment.getWaveData(), 379 mSamplingRate); 380 } 381 382 log("got message native buffer test rec complete!!"); 383 refreshPlots(); 384 refreshState(); 385 //rounded up number of seconds elapsed 386 mBufferTestElapsedSeconds = 387 (int) ((System.currentTimeMillis() - mBufferTestStartTime + 388 Constant.MILLIS_PER_SECOND - 1) / Constant.MILLIS_PER_SECOND); 389 switch (msg.what) { 390 case NativeAudioThread. 391 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS: 392 case NativeAudioThread. 393 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS: 394 showToastImportant("Native Test Completed with Fatal Errors"); 395 break; 396 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 397 case NativeAudioThread. 398 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 399 showToast("Native Test Stopped"); 400 break; 401 default: 402 showToast("Native Test Completed"); 403 break; 404 } 405 406 407 stopAudioTestThreads(); 408 if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) { 409 saveAllTo(mIntentFileName); 410 } 411 mIntentRunning = false; 412 413 if (getApp().isCaptureEnabled()) { 414 mCaptureHolder.stopLoopbackListenerScript(); 415 } 416 } // mNativeAudioThread != null 417 refreshSoundLevelBar(); 418 break; 419 420 default: 421 log("Got message:" + msg.what); 422 break; 423 } 424 425 // Control UI elements visibility specific to latency or buffer/glitch test 426 switch (msg.what) { 427 428 // Latency test started 429 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 430 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED: 431 setTransportButtonsState(LATENCY_TEST_STARTED); 432 break; 433 434 // Latency test ended 435 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 436 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 437 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 438 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR: 439 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE: 440 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP: 441 case NativeAudioThread. 442 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS: 443 setTransportButtonsState(LATENCY_TEST_ENDED); 444 if (mIsCTSTest) { 445 Intent intent = getIntent(); 446 intent.putExtra("RoundTripTime", mCorrelation.mEstimatedLatencyMs); 447 intent.putExtra("Confidence", mCorrelation.mEstimatedLatencyConfidence); 448 setResult(RESULT_OK, intent); 449 finish(); 450 } 451 break; 452 453 // Buffer test started 454 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 455 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED: 456 setTransportButtonsState(BUFFER_TEST_STARTED); 457 break; 458 459 // Buffer test ended 460 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 461 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 462 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 463 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR: 464 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE: 465 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP: 466 case NativeAudioThread. 467 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS: 468 setTransportButtonsState(BUFFER_TEST_ENDED); 469 break; 470 471 // Sound Calibration started 472 case CALIBRATION_STARTED: 473 setTransportButtonsState(CALIBRATION_STARTED); 474 break; 475 476 // Sound Calibration ended 477 case CALIBRATION_ENDED: 478 setTransportButtonsState(CALIBRATION_ENDED); 479 break; 480 481 default: 482 log("Got message:" + msg.what); 483 break; 484 } 485 } 486 }; 487 488 public static class RetainedFragment extends Fragment { 489 private double[] mWaveData; // this is where we store the data for the wave plot 490 491 // this method is only called once for this fragment 492 @Override onCreate(Bundle savedInstanceState)493 public void onCreate(Bundle savedInstanceState) { 494 super.onCreate(savedInstanceState); 495 setRetainInstance(true); 496 } 497 setWaveData(double[] waveData)498 public void setWaveData(double[] waveData) { 499 this.mWaveData = waveData; 500 } 501 getWaveData()502 public double[] getWaveData() { 503 return mWaveData; 504 } 505 } 506 507 @Override onCreate(Bundle savedInstanceState)508 public void onCreate(Bundle savedInstanceState) { 509 super.onCreate(savedInstanceState); 510 511 // Set the layout for this activity. You can find it 512 View view = getLayoutInflater().inflate(R.layout.main_activity, null); 513 setContentView(view); 514 515 // find the retained fragment on activity restarts 516 FragmentManager fm = getFragmentManager(); 517 mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT); 518 // create the fragment and data the first time 519 if (mRetainedFragment == null) { 520 mRetainedFragment = new RetainedFragment(); 521 fm.beginTransaction().add(mRetainedFragment, TAG_RETAINED_FRAGMENT).commit(); 522 } 523 524 // TODO: Write script to file at more appropriate time, from settings activity or intent 525 // TODO: Respond to failure with more than just a toast 526 if (hasWriteFilePermission()) { 527 boolean successfulWrite = AtraceScriptsWriter.writeScriptsToFile(this); 528 if (!successfulWrite) { 529 showToastImportant("Unable to write loopback_listener script to device"); 530 } 531 } else { 532 requestWriteFilePermission(PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT); 533 } 534 535 536 mTextInfo = (TextView) findViewById(R.id.textInfo); 537 mBarMasterLevel = (SeekBar) findViewById(R.id.BarMasterLevel); 538 539 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 540 int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 541 mBarMasterLevel.setMax(maxVolume); 542 543 mBarMasterLevel.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 544 @Override 545 public void onStopTrackingTouch(SeekBar seekBar) { 546 } 547 548 @Override 549 public void onStartTrackingTouch(SeekBar seekBar) { 550 } 551 552 @Override 553 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 554 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 555 am.setStreamVolume(AudioManager.STREAM_MUSIC, 556 progress, 0); 557 refreshSoundLevelBar(); 558 log("Changed stream volume to: " + progress); 559 } 560 }); 561 mWavePlotView = (WavePlotView) findViewById(R.id.viewWavePlot); 562 563 mTextViewCurrentLevel = (TextView) findViewById(R.id.textViewCurrentLevel); 564 mTextViewCurrentLevel.setTextSize(15); 565 566 mTextViewResultSummary = (TextView) findViewById(R.id.resultSummary); 567 refreshSoundLevelBar(); 568 569 if (savedInstanceState != null) { 570 restoreInstanceState(savedInstanceState); 571 } 572 573 if (!hasRecordAudioPermission()) { 574 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY); 575 } 576 577 setTransportButtonsState(NO_TEST_ACTIVE); 578 579 applyIntent(getIntent()); 580 } 581 582 @Override onStart()583 protected void onStart() { 584 super.onStart(); 585 Intent audioTestIntent = new Intent(this, AudioTestService.class); 586 startService(audioTestIntent); 587 boolean bound = bindService(audioTestIntent, mServiceConnection, Context.BIND_AUTO_CREATE); 588 if (bound) { 589 log("Successfully bound to service!"); 590 } else { 591 log("Failed to bind service!"); 592 } 593 } 594 595 596 @Override onStop()597 protected void onStop() { 598 super.onStop(); 599 log("Activity on stop!"); 600 // Unbind from the service 601 if (mBound) { 602 unbindService(mServiceConnection); 603 mBound = false; 604 } 605 } 606 607 @Override onNewIntent(Intent intent)608 public void onNewIntent(Intent intent) { 609 log("On New Intent called!"); 610 applyIntent(intent); 611 } 612 613 614 /** 615 * This method will be called whenever the test starts running (either by operating on the 616 * device or by adb command). In the case where the test is started through adb command, 617 * adb parameters will be read into intermediate variables. 618 */ applyIntent(Intent intent)619 private void applyIntent(Intent intent) { 620 Bundle b = intent.getExtras(); 621 622 if (b != null) { 623 for (String key: b.keySet()) { 624 Log.d (TAG, key + " is a key in the bundle"); 625 } 626 } 627 628 if (b != null && !mIntentRunning) { 629 // adb shell am start -n org.drrickorang.loopback/.LoopbackActivity 630 // --ei SF 48000 --es FileName test1 --ei RecorderBuffer 512 --ei PlayerBuffer 512 631 // --ei AudioThread 1 --ei MicSource 3 --ei AudioLevel 12 632 // --ei TestType 223 --ei BufferTestDuration 60 --ei NumLoadThreads 4 633 // --ei CI -1 --ez CaptureSysTrace true --ez CaptureWavs false --ei NumCaptures 5 634 // --ei WavDuration 15 635 636 // Note: for native mode, player and recorder buffer sizes are the same, and can only be 637 // set through player buffer size 638 639 boolean hasRecordAudioPermission = hasRecordAudioPermission(); 640 boolean hasWriteFilePermission = hasWriteFilePermission(); 641 if (!hasRecordAudioPermission || !hasWriteFilePermission) { 642 if (!hasRecordAudioPermission) { 643 log("Missing Permission: RECORD_AUDIO"); 644 } 645 646 if (!hasWriteFilePermission) { 647 log("Missing Permission: WRITE_EXTERNAL_STORAGE"); 648 } 649 650 return; 651 } 652 653 if (b.containsKey(INTENT_AUDIO_THREAD)) { 654 int newAudioThreadType = b.getInt(INTENT_AUDIO_THREAD); 655 if (newAudioThreadType != getApp().getAudioThreadType()) { 656 getApp().setAudioThreadType(newAudioThreadType); 657 getApp().computeDefaults(); 658 } 659 mIntentRunning = true; 660 } 661 662 if (b.containsKey(INTENT_PERFORMANCE_MODE)) { 663 int newPerformanceMode = b.getInt(INTENT_PERFORMANCE_MODE); 664 if (newPerformanceMode != getApp().getPerformanceMode()) { 665 getApp().setPerformanceMode(newPerformanceMode); 666 getApp().computeDefaults(); 667 } 668 mIntentRunning = true; 669 } 670 671 if (b.containsKey(INTENT_BUFFER_TEST_DURATION)) { 672 getApp().setBufferTestDuration(b.getInt(INTENT_BUFFER_TEST_DURATION)); 673 mIntentRunning = true; 674 } 675 676 if (b.containsKey(INTENT_SAMPLING_FREQUENCY)) { 677 getApp().setSamplingRate(b.getInt(INTENT_SAMPLING_FREQUENCY)); 678 mIntentRunning = true; 679 } 680 681 if (b.containsKey(INTENT_CORRELATION_BLOCK_SIZE)) { 682 mCorrelation.setBlockSize(b.getInt(INTENT_CORRELATION_BLOCK_SIZE)); 683 mIntentRunning = true; 684 } 685 686 if (b.containsKey(INTENT_CHANNEL_INDEX)) { 687 getApp().setChannelIndex(b.getInt(INTENT_CHANNEL_INDEX)); 688 mChannelIndex = b.getInt(INTENT_CHANNEL_INDEX); 689 mIntentRunning = true; 690 } 691 692 if (b.containsKey(INTENT_FILENAME)) { 693 mIntentFileName = b.getString(INTENT_FILENAME); 694 mIntentRunning = true; 695 } 696 697 if (b.containsKey(INTENT_RECORDER_BUFFER)) { 698 getApp().setRecorderBufferSizeInBytes( 699 b.getInt(INTENT_RECORDER_BUFFER) * Constant.BYTES_PER_FRAME); 700 mIntentRunning = true; 701 } 702 703 if (b.containsKey(INTENT_PLAYER_BUFFER)) { 704 getApp().setPlayerBufferSizeInBytes( 705 b.getInt(INTENT_PLAYER_BUFFER) * Constant.BYTES_PER_FRAME); 706 mIntentRunning = true; 707 } 708 709 if (b.containsKey(INTENT_MIC_SOURCE)) { 710 getApp().setMicSource(b.getInt(INTENT_MIC_SOURCE)); 711 mIntentRunning = true; 712 } 713 714 if (b.containsKey(INTENT_IGNORE_FIRST_FRAMES)) { 715 getApp().setIgnoreFirstFrames(b.getInt(INTENT_IGNORE_FIRST_FRAMES)); 716 mIntentRunning = true; 717 } 718 719 if (b.containsKey(INTENT_AUDIO_LEVEL)) { 720 int audioLevel = b.getInt(INTENT_AUDIO_LEVEL); 721 if (audioLevel >= 0) { 722 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 723 am.setStreamVolume(AudioManager.STREAM_MUSIC, audioLevel, 0); 724 getApp().setSoundLevelCalibrationEnabled(false); 725 } else { // AudioLevel of -1 means automatically calibrate 726 getApp().setSoundLevelCalibrationEnabled(true); 727 } 728 mIntentRunning = true; 729 } 730 731 if (b.containsKey(INTENT_NUMBER_LOAD_THREADS)) { 732 getApp().setNumberOfLoadThreads(b.getInt(INTENT_NUMBER_LOAD_THREADS)); 733 mIntentRunning = true; 734 } 735 736 if (b.containsKey(INTENT_ENABLE_SYSTRACE)) { 737 getApp().setCaptureSysTraceEnabled(b.getBoolean(INTENT_ENABLE_SYSTRACE)); 738 mIntentRunning = true; 739 } 740 741 if (b.containsKey(INTENT_ENABLE_WAVCAPTURE)) { 742 getApp().setCaptureWavsEnabled(b.getBoolean(INTENT_ENABLE_WAVCAPTURE)); 743 mIntentRunning = true; 744 } 745 746 if (b.containsKey(INTENT_NUM_CAPTURES)) { 747 getApp().setNumberOfCaptures(b.getInt(INTENT_NUM_CAPTURES)); 748 mIntentRunning = true; 749 } 750 751 if (b.containsKey(INTENT_WAV_DURATION)) { 752 getApp().setBufferTestWavePlotDuration(b.getInt(INTENT_WAV_DURATION)); 753 mIntentRunning = true; 754 } 755 756 if (b.containsKey(INTENT_USB_AUDIO_ROUTE)) { 757 waitForUsbRoute(); 758 } 759 760 if (mIntentRunning || b.containsKey(INTENT_TEST_TYPE)) { 761 // run tests with provided or default parameters 762 refreshState(); 763 764 // if no test is specified then Latency Test will be run 765 int testType = b.getInt(INTENT_TEST_TYPE, 766 Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY); 767 switch (testType) { 768 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 769 startBufferTest(); 770 break; 771 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_CALIBRATION: 772 doCalibration(); 773 break; 774 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 775 default: 776 if (b.containsKey(Constant.KEY_CTSINVOCATION)) { 777 mIsCTSTest = true; 778 mCtsNumIterations = b.getInt(Constant.KEY_NUMITERATIONS); 779 } 780 startLatencyTest(); 781 break; 782 } 783 } 784 785 } else { 786 if (mIntentRunning && b != null) { 787 log("Test already in progress"); 788 showToast("Test already in progress"); 789 } 790 } 791 } 792 793 794 /** Stop all currently running threads that are related to audio test. */ stopAudioTestThreads()795 private void stopAudioTestThreads() { 796 log("stopping audio threads"); 797 if (mAudioThread != null) { 798 try { 799 mAudioThread.finish(); 800 mAudioThread.join(Constant.JOIN_WAIT_TIME_MS); 801 } catch (InterruptedException e) { 802 e.printStackTrace(); 803 } 804 mAudioThread = null; 805 } 806 807 if (mNativeAudioThread != null) { 808 try { 809 mNativeAudioThread.finish(); 810 mNativeAudioThread.join(Constant.JOIN_WAIT_TIME_MS); 811 } catch (InterruptedException e) { 812 e.printStackTrace(); 813 } 814 mNativeAudioThread = null; 815 } 816 817 stopLoadThreads(); 818 System.gc(); 819 } 820 821 onDestroy()822 public void onDestroy() { 823 stopAudioTestThreads(); 824 super.onDestroy(); 825 stopService(new Intent(this, AudioTestService.class)); 826 } 827 828 829 @Override onResume()830 protected void onResume() { 831 super.onResume(); 832 log("on resume called"); 833 setTransportButtonsState(NO_TEST_ACTIVE); 834 } 835 836 837 // @Override 838 // protected void onPause() { 839 // super.onPause(); 840 // // this means that this activity will not be recreated now, user is leaving it 841 // // or the activity is otherwise finishing 842 // if(isFinishing()) { 843 // FragmentManager fm = getFragmentManager(); 844 // // we will not need this fragment anymore, this may also be a good place to signal 845 // // to the retained fragment object to perform its own cleanup. 846 // fm.beginTransaction().remove(mRetainedFragment).commit(); 847 // } 848 // } 849 850 @Override onCreateOptionsMenu(Menu menu)851 public boolean onCreateOptionsMenu(Menu menu) { 852 MenuInflater inflater = getMenuInflater(); 853 inflater.inflate(R.menu.tool_bar_menu, menu); 854 return true; 855 } 856 857 @Override onOptionsItemSelected(MenuItem item)858 public boolean onOptionsItemSelected(MenuItem item) { 859 // Respond to user selecting action bar buttons 860 switch (item.getItemId()) { 861 case R.id.action_help: 862 if (!isBusy()) { 863 // Launch about Activity 864 Intent aboutIntent = new Intent(this, AboutActivity.class); 865 startActivity(aboutIntent); 866 } else { 867 showToast("Test in progress... please wait"); 868 } 869 870 return true; 871 872 case R.id.action_settings: 873 if (!isBusy()) { 874 // Launch settings activity 875 Intent mySettingsIntent = new Intent(this, SettingsActivity.class); 876 startActivityForResult(mySettingsIntent, SETTINGS_ACTIVITY_REQUEST); 877 } else { 878 showToast("Test in progress... please wait"); 879 } 880 return true; 881 882 default: 883 break; 884 } 885 886 return super.onOptionsItemSelected(item); 887 } 888 889 890 /** Check if the app is busy (running test). */ isBusy()891 public boolean isBusy() { 892 boolean busy = false; 893 894 if (mAudioThread != null && mAudioThread.mIsRunning) { 895 busy = true; 896 } 897 898 if (mNativeAudioThread != null && mNativeAudioThread.mIsRunning) { 899 busy = true; 900 } 901 902 return busy; 903 } 904 905 906 /** Create a new audio thread according to the settings. */ restartAudioSystem()907 private void restartAudioSystem() { 908 log("restart audio system..."); 909 910 int sessionId = 0; /* FIXME runtime test for am.generateAudioSessionId() in API 21 */ 911 912 mAudioThreadType = getApp().getAudioThreadType(); 913 mSamplingRate = getApp().getSamplingRate(); 914 mChannelIndex = getApp().getChannelIndex(); 915 mPlayerBufferSizeInBytes = getApp().getPlayerBufferSizeInBytes(); 916 mRecorderBufferSizeInBytes = getApp().getRecorderBufferSizeInBytes(); 917 mTestStartTimeString = (String) DateFormat.format("MMddkkmmss", 918 System.currentTimeMillis()); 919 mMicSource = getApp().getMicSource(); 920 mPerformanceMode = getApp().getPerformanceMode(); 921 mIgnoreFirstFrames = getApp().getIgnoreFirstFrames(); 922 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 923 mSoundLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC); 924 mBufferTestDurationInSeconds = getApp().getBufferTestDuration(); 925 mBufferTestWavePlotDurationInSeconds = getApp().getBufferTestWavePlotDuration(); 926 927 mCaptureHolder = new CaptureHolder(getApp().getNumStateCaptures(), 928 getFileNamePrefix(), getApp().isCaptureWavSnippetsEnabled(), 929 getApp().isCaptureSysTraceEnabled(), getApp().isCaptureBugreportEnabled(), 930 this, mSamplingRate); 931 932 log(" current sampling rate: " + mSamplingRate); 933 stopAudioTestThreads(); 934 935 // select java or native audio thread 936 int micSourceMapped; 937 938 switch (mAudioThreadType) { 939 940 case Constant.AUDIO_THREAD_TYPE_JAVA: 941 micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_JAVA, mMicSource); 942 943 int expectedRecorderBufferPeriod = Math.round( 944 (float) (mRecorderBufferSizeInBytes * Constant.MILLIS_PER_SECOND) 945 / (Constant.BYTES_PER_FRAME * mSamplingRate)); 946 mRecorderBufferPeriod.prepareMemberObjects( 947 Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds, 948 expectedRecorderBufferPeriod, mCaptureHolder); 949 950 int expectedPlayerBufferPeriod = Math.round( 951 (float) (mPlayerBufferSizeInBytes * Constant.MILLIS_PER_SECOND) 952 / (Constant.BYTES_PER_FRAME * mSamplingRate)); 953 mPlayerBufferPeriod.prepareMemberObjects( 954 Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds, 955 expectedPlayerBufferPeriod, mCaptureHolder); 956 957 mAudioThread = new LoopbackAudioThread(mSamplingRate, mPlayerBufferSizeInBytes, 958 mRecorderBufferSizeInBytes, micSourceMapped, 959 /* no performance mode */ mRecorderBufferPeriod, 960 mPlayerBufferPeriod, mTestType, mBufferTestDurationInSeconds, 961 mBufferTestWavePlotDurationInSeconds, getApplicationContext(), 962 mChannelIndex, mCaptureHolder); 963 mAudioThread.setMessageHandler(mMessageHandler); 964 mAudioThread.mSessionId = sessionId; 965 mAudioThread.start(); 966 break; 967 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 968 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 969 micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_NATIVE_SLES, 970 mMicSource); 971 int performanceModeMapped = getApp().mapPerformanceMode(mPerformanceMode); 972 // Note: mRecorderBufferSizeInBytes will not actually be used, since recorder buffer 973 // size = player buffer size in native mode 974 mNativeAudioThread = new NativeAudioThread(mAudioThreadType, mSamplingRate, 975 mPlayerBufferSizeInBytes, mRecorderBufferSizeInBytes, 976 micSourceMapped, performanceModeMapped, mTestType, 977 mBufferTestDurationInSeconds, mBufferTestWavePlotDurationInSeconds, 978 mIgnoreFirstFrames, mCaptureHolder); 979 mNativeAudioThread.setMessageHandler(mMessageHandler); 980 mNativeAudioThread.mSessionId = sessionId; 981 mNativeAudioThread.start(); 982 break; 983 } 984 985 startLoadThreads(); 986 987 mMessageHandler.post(new Runnable() { 988 @Override 989 public void run() { 990 refreshState(); 991 } 992 }); 993 } 994 995 996 /** Start all LoadThread. */ startLoadThreads()997 private void startLoadThreads() { 998 999 if (getApp().getNumberOfLoadThreads() > 0) { 1000 1001 mLoadThreads = new LoadThread[getApp().getNumberOfLoadThreads()]; 1002 1003 for (int i = 0; i < mLoadThreads.length; i++) { 1004 mLoadThreads[i] = new LoadThread("Loopback_LoadThread_" + i); 1005 mLoadThreads[i].start(); 1006 } 1007 } 1008 } 1009 1010 1011 /** Stop all LoadThread. */ stopLoadThreads()1012 private void stopLoadThreads() { 1013 log("stopping load threads"); 1014 if (mLoadThreads != null) { 1015 for (int i = 0; i < mLoadThreads.length; i++) { 1016 if (mLoadThreads[i] != null) { 1017 try { 1018 mLoadThreads[i].requestStop(); 1019 mLoadThreads[i].join(Constant.JOIN_WAIT_TIME_MS); 1020 } catch (InterruptedException e) { 1021 e.printStackTrace(); 1022 } 1023 mLoadThreads[i] = null; 1024 } 1025 } 1026 } 1027 } 1028 1029 resetBufferPeriodRecord(BufferPeriod recorderBufferPeriod, BufferPeriod playerBufferPeriod)1030 private void resetBufferPeriodRecord(BufferPeriod recorderBufferPeriod, 1031 BufferPeriod playerBufferPeriod) { 1032 recorderBufferPeriod.resetRecord(); 1033 playerBufferPeriod.resetRecord(); 1034 } 1035 canPerformBufferTest()1036 private boolean canPerformBufferTest() { 1037 // Retrieve the thread type from the app, because mAudioThreadType 1038 // only gets populated during the start of the test. 1039 switch (getApp().getAudioThreadType()) { 1040 case Constant.AUDIO_THREAD_TYPE_JAVA: 1041 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1042 return true; 1043 } 1044 // Buffer test isn't yet implemented for AAudio. 1045 return false; 1046 } 1047 setTransportButtonsState(int state)1048 private void setTransportButtonsState(int state) { 1049 Button latencyStart = (Button) findViewById(R.id.buttonStartLatencyTest); 1050 Button bufferStart = (Button) findViewById(R.id.buttonStartBufferTest); 1051 Button calibrationStart = (Button) findViewById(R.id.buttonCalibrateSoundLevel); 1052 1053 boolean canEnableBufferTest = canPerformBufferTest(); 1054 1055 switch (state) { 1056 case NO_TEST_ACTIVE: 1057 latencyStart.setEnabled(true); 1058 bufferStart.setEnabled(canEnableBufferTest); 1059 calibrationStart.setEnabled(true); 1060 break; 1061 1062 case LATENCY_TEST_STARTED: 1063 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE); 1064 mTextViewResultSummary.setText(""); 1065 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE); 1066 latencyStart.setCompoundDrawablesWithIntrinsicBounds( 1067 R.drawable.ic_stop, 0, 0, 0); 1068 bufferStart.setEnabled(false); 1069 calibrationStart.setEnabled(false); 1070 break; 1071 1072 case LATENCY_TEST_ENDED: 1073 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 1074 latencyStart.setCompoundDrawablesWithIntrinsicBounds( 1075 R.drawable.ic_play_arrow, 0, 0, 0); 1076 bufferStart.setEnabled(canEnableBufferTest); 1077 calibrationStart.setEnabled(true); 1078 break; 1079 1080 case BUFFER_TEST_STARTED: 1081 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE); 1082 mTextViewResultSummary.setText(""); 1083 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE); 1084 bufferStart.setCompoundDrawablesWithIntrinsicBounds( 1085 R.drawable.ic_stop, 0, 0, 0); 1086 latencyStart.setEnabled(false); 1087 calibrationStart.setEnabled(false); 1088 break; 1089 1090 case BUFFER_TEST_ENDED: 1091 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 1092 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE); 1093 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE); 1094 bufferStart.setCompoundDrawablesWithIntrinsicBounds( 1095 R.drawable.ic_play_arrow, 0, 0, 0); 1096 latencyStart.setEnabled(true); 1097 calibrationStart.setEnabled(true); 1098 break; 1099 1100 case CALIBRATION_STARTED: 1101 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE); 1102 findViewById(R.id.resultSummary).setVisibility(View.INVISIBLE); 1103 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE); 1104 calibrationStart.setCompoundDrawablesWithIntrinsicBounds( 1105 R.drawable.ic_stop, 0, 0, 0); 1106 latencyStart.setEnabled(false); 1107 bufferStart.setEnabled(false); 1108 calibrationStart.setEnabled(false); 1109 break; 1110 1111 case CALIBRATION_ENDED: 1112 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 1113 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE); 1114 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE); 1115 calibrationStart.setCompoundDrawablesWithIntrinsicBounds( 1116 R.drawable.ic_play_arrow, 0, 0, 0); 1117 latencyStart.setEnabled(true); 1118 bufferStart.setEnabled(canEnableBufferTest); 1119 calibrationStart.setEnabled(true); 1120 break; 1121 1122 default: 1123 break; 1124 } 1125 } 1126 doCalibrationIfEnabled(final Runnable onComplete)1127 private void doCalibrationIfEnabled(final Runnable onComplete) { 1128 if (getApp().isSoundLevelCalibrationEnabled()) { 1129 doCalibration(onComplete); 1130 } else { 1131 if (onComplete != null) { 1132 onComplete.run(); 1133 } 1134 } 1135 } 1136 doCalibration()1137 private void doCalibration() { 1138 doCalibration(null); 1139 } 1140 doCalibration(final Runnable onComplete)1141 private void doCalibration(final Runnable onComplete) { 1142 if (isBusy()) { 1143 showToast("Test in progress... please wait"); 1144 return; 1145 } 1146 1147 if (!hasRecordAudioPermission()) { 1148 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY); 1149 // Returning, otherwise we don't know if user accepted or rejected. 1150 return; 1151 } 1152 1153 showToast("Calibrating sound level..."); 1154 final SoundLevelCalibration calibration = 1155 new SoundLevelCalibration(getApp().getAudioThreadType(), 1156 getApp().getSamplingRate(), 1157 getApp().getPlayerBufferSizeInBytes(), 1158 getApp().getRecorderBufferSizeInBytes(), 1159 getApp().getMicSource(), getApp().getPerformanceMode(), this); 1160 1161 calibration.setChangeListener(new SoundLevelCalibration.SoundLevelChangeListener() { 1162 @Override 1163 void onChange(int newLevel) { 1164 refreshSoundLevelBar(); 1165 } 1166 }); 1167 1168 mCalibrationThread = new Thread(new Runnable() { 1169 @Override 1170 public void run() { 1171 calibration.calibrate(); 1172 showToast("Calibration complete"); 1173 if (onComplete != null) { 1174 onComplete.run(); 1175 } 1176 } 1177 }); 1178 1179 mCalibrationThread.start(); 1180 } 1181 1182 /** Start the latency test. */ onButtonLatencyTest(View view)1183 public void onButtonLatencyTest(View view) throws InterruptedException { 1184 if (isBusy()) { 1185 stopTests(); 1186 return; 1187 } 1188 1189 // Ensure we have RECORD_AUDIO permissions 1190 // On Android M (API 23) we must request dangerous permissions each time we use them 1191 if (hasRecordAudioPermission()) { 1192 startLatencyTest(); 1193 } else { 1194 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY); 1195 } 1196 } 1197 startLatencyTest()1198 private void startLatencyTest() { 1199 if (isBusy()) { 1200 showToast("Test in progress... please wait"); 1201 return; 1202 } 1203 1204 doCalibrationIfEnabled(latencyTestRunnable); 1205 } 1206 1207 private Runnable latencyTestRunnable = new Runnable() { 1208 @Override 1209 public void run() { 1210 if (isBusy()) { 1211 showToast("Test in progress... please wait"); 1212 return; 1213 } 1214 1215 mBarMasterLevel.post(new Runnable() { 1216 @Override 1217 public void run() { 1218 mBarMasterLevel.setEnabled(false); 1219 } 1220 }); 1221 resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod); 1222 1223 mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY; 1224 restartAudioSystem(); 1225 try { 1226 Thread.sleep(THREAD_SLEEP_DURATION_MS); 1227 } catch (InterruptedException e) { 1228 e.printStackTrace(); 1229 } 1230 1231 switch (mAudioThreadType) { 1232 case Constant.AUDIO_THREAD_TYPE_JAVA: 1233 if (mAudioThread != null) { 1234 mAudioThread.runTest(); 1235 } 1236 break; 1237 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1238 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1239 if (mNativeAudioThread != null) { 1240 mNativeAudioThread.runTest(); 1241 } 1242 break; 1243 default: 1244 break; 1245 } 1246 } 1247 }; 1248 1249 1250 /** Start the Buffer (Glitch Detection) Test. */ onButtonBufferTest(View view)1251 public void onButtonBufferTest(View view) throws InterruptedException { 1252 if (isBusy()) { 1253 stopTests(); 1254 return; 1255 } 1256 1257 if (hasRecordAudioPermission()) { 1258 startBufferTest(); 1259 } else { 1260 requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER); 1261 } 1262 } 1263 1264 startBufferTest()1265 private void startBufferTest() { 1266 // In the interactive mode the buffer test button should be disabled 1267 // if the buffer test isn't supported, but the function can also be invoked 1268 // via an intent. 1269 if (!canPerformBufferTest()) { 1270 showToastImportant("Buffer test is not supported with this thread type"); 1271 log("Buffer test is not supported with this thread type"); 1272 return; 1273 } 1274 1275 if (!isBusy()) { 1276 mBarMasterLevel.setEnabled(false); 1277 resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod); 1278 mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD; 1279 restartAudioSystem(); // in this function a audio thread is created 1280 try { 1281 Thread.sleep(THREAD_SLEEP_DURATION_MS); 1282 } catch (InterruptedException e) { 1283 e.printStackTrace(); 1284 } 1285 1286 switch (mAudioThreadType) { 1287 case Constant.AUDIO_THREAD_TYPE_JAVA: 1288 if (mAudioThread != null) { 1289 mAudioThread.runBufferTest(); 1290 } 1291 break; 1292 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1293 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1294 if (mNativeAudioThread != null) { 1295 mNativeAudioThread.runBufferTest(); 1296 } 1297 break; 1298 default: 1299 break; 1300 } 1301 } else { 1302 int duration = 0; 1303 switch (mAudioThreadType) { 1304 case Constant.AUDIO_THREAD_TYPE_JAVA: 1305 duration = mAudioThread.getDurationInSeconds(); 1306 break; 1307 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1308 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1309 duration = mNativeAudioThread.getDurationInSeconds(); 1310 break; 1311 default: 1312 break; 1313 } 1314 showToastImportant("Long-run Test in progress, in total should take " + 1315 Integer.toString(duration) + "s, please wait"); 1316 } 1317 } 1318 1319 1320 /** Stop the ongoing test. */ stopTests()1321 public void stopTests() throws InterruptedException { 1322 if (mAudioThread != null) { 1323 mAudioThread.requestStopTest(); 1324 } 1325 1326 if (mNativeAudioThread != null) { 1327 mNativeAudioThread.requestStopTest(); 1328 } 1329 } 1330 onButtonCalibrateSoundLevel(final View view)1331 public void onButtonCalibrateSoundLevel(final View view) { 1332 Message m = Message.obtain(); 1333 m.what = CALIBRATION_STARTED; 1334 mMessageHandler.sendMessage(m); 1335 Runnable onComplete = new Runnable() { 1336 @Override 1337 public void run() { 1338 Message m = Message.obtain(); 1339 m.what = CALIBRATION_ENDED; 1340 mMessageHandler.sendMessage(m); 1341 } 1342 }; 1343 doCalibration(onComplete); 1344 } 1345 1346 /*** 1347 * Show dialog to choose to save files with filename dialog or not 1348 */ onButtonSave(View view)1349 public void onButtonSave(View view) { 1350 if (!isBusy()) { 1351 DialogFragment newFragment = new SaveFilesDialogFragment(); 1352 newFragment.show(getFragmentManager(), "saveFiles"); 1353 } else { 1354 showToast("Test in progress... please wait"); 1355 } 1356 } 1357 1358 /** 1359 * Save five files: one .png file for a screenshot on the main activity, one .wav file for 1360 * the plot displayed on the main activity, one .txt file for storing various test results, one 1361 * .txt file for storing recorder buffer period data, and one .txt file for storing player 1362 * buffer period data. 1363 */ SaveFilesWithDialog()1364 private void SaveFilesWithDialog() { 1365 1366 String fileName = "loopback_" + mTestStartTimeString; 1367 1368 //Launch filename choosing activities if available, otherwise save without prompting 1369 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 1370 launchFileNameChoosingActivity("text/plain", fileName, ".txt", SAVE_TO_TXT_REQUEST); 1371 launchFileNameChoosingActivity("image/png", fileName, ".png", SAVE_TO_PNG_REQUEST); 1372 launchFileNameChoosingActivity("audio/wav", fileName, ".wav", SAVE_TO_WAVE_REQUEST); 1373 launchFileNameChoosingActivity("text/plain", fileName, "_recorderBufferPeriod.txt", 1374 SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST); 1375 launchFileNameChoosingActivity("text/plain", fileName, "_recorderBufferPeriodTimes.txt", 1376 SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST); 1377 launchFileNameChoosingActivity("image/png", fileName, "_recorderBufferPeriod.png", 1378 SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST); 1379 launchFileNameChoosingActivity("text/plain", fileName, "_playerBufferPeriod.txt", 1380 SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST); 1381 launchFileNameChoosingActivity("text/plain", fileName, "_playerBufferPeriodTimes.txt", 1382 SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST); 1383 launchFileNameChoosingActivity("image/png", fileName, "_playerBufferPeriod.png", 1384 SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST); 1385 1386 if (mGlitchesData != null) { 1387 launchFileNameChoosingActivity("text/plain", fileName, "_glitchMillis.txt", 1388 SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST); 1389 launchFileNameChoosingActivity("image/png", fileName, "_heatMap.png", 1390 SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST); 1391 } 1392 } else { 1393 saveAllTo(fileName); 1394 } 1395 } 1396 1397 /** 1398 * Launches an activity for choosing the filename of the file to be saved 1399 */ launchFileNameChoosingActivity(String type, String fileName, String suffix, int RequestCode)1400 public void launchFileNameChoosingActivity(String type, String fileName, String suffix, 1401 int RequestCode) { 1402 Intent FilenameIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 1403 FilenameIntent.addCategory(Intent.CATEGORY_OPENABLE); 1404 FilenameIntent.setType(type); 1405 FilenameIntent.putExtra(Intent.EXTRA_TITLE, fileName + suffix); 1406 startActivityForResult(FilenameIntent, RequestCode); 1407 } 1408 getFileNamePrefix()1409 private String getFileNamePrefix() { 1410 if (mIntentFileName != null && !mIntentFileName.isEmpty()) { 1411 return mIntentFileName; 1412 } else { 1413 return "loopback_" + mTestStartTimeString; 1414 } 1415 } 1416 1417 /** See the documentation on onButtonSave() */ saveAllTo(String fileName)1418 public void saveAllTo(String fileName) { 1419 1420 if (!hasWriteFilePermission()) { 1421 requestWriteFilePermission(PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS); 1422 return; 1423 } 1424 1425 showToast("Saving files to: " + fileName + ".(wav,png,txt)"); 1426 1427 //save to a given uri... local file? 1428 saveToWaveFile(Uri.parse(FILE_SAVE_PATH + fileName + ".wav")); 1429 1430 saveScreenShot(Uri.parse(FILE_SAVE_PATH + fileName + ".png")); 1431 1432 saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + ".txt"), getReport().toString()); 1433 1434 int[] bufferPeriodArray = null; 1435 int maxBufferPeriod = Constant.UNKNOWN; 1436 switch (mAudioThreadType) { 1437 case Constant.AUDIO_THREAD_TYPE_JAVA: 1438 bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray(); 1439 maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod(); 1440 break; 1441 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1442 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1443 bufferPeriodArray = mNativeRecorderBufferPeriodArray; 1444 maxBufferPeriod = mNativeRecorderMaxBufferPeriod; 1445 break; 1446 default: 1447 break; 1448 } 1449 saveBufferPeriod(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriod.txt"), 1450 bufferPeriodArray, maxBufferPeriod); 1451 saveHistogram(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriod.png"), 1452 bufferPeriodArray, maxBufferPeriod); 1453 saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriodTimes.txt"), 1454 mRecorderCallbackTimes.toString()); 1455 1456 bufferPeriodArray = null; 1457 maxBufferPeriod = Constant.UNKNOWN; 1458 switch (mAudioThreadType) { 1459 case Constant.AUDIO_THREAD_TYPE_JAVA: 1460 bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray(); 1461 maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod(); 1462 break; 1463 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1464 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1465 bufferPeriodArray = mNativePlayerBufferPeriodArray; 1466 maxBufferPeriod = mNativePlayerMaxBufferPeriod; 1467 break; 1468 default: 1469 break; 1470 } 1471 saveBufferPeriod(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriod.txt") 1472 , bufferPeriodArray, maxBufferPeriod); 1473 saveHistogram(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriod.png"), 1474 bufferPeriodArray, maxBufferPeriod); 1475 saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriodTimes.txt"), 1476 mPlayerCallbackTimes.toString()); 1477 1478 if (mGlitchesData != null) { 1479 saveGlitchOccurrences(Uri.parse(FILE_SAVE_PATH + fileName + "_glitchMillis.txt"), 1480 mGlitchesData); 1481 saveHeatMap(Uri.parse(FILE_SAVE_PATH + fileName + "_heatMap.png"), 1482 mRecorderCallbackTimes, mPlayerCallbackTimes, 1483 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize, 1484 mFFTOverlapSamples, mGlitchesData, mSamplingRate), 1485 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, fileName); 1486 } 1487 1488 } 1489 1490 1491 @Override onActivityResult(int requestCode, int resultCode, Intent resultData)1492 public void onActivityResult(int requestCode, int resultCode, Intent resultData) { 1493 log("ActivityResult request: " + requestCode + " result:" + resultCode); 1494 1495 if (resultCode == Activity.RESULT_OK) { 1496 switch (requestCode) { 1497 1498 case SAVE_TO_WAVE_REQUEST: 1499 log("got SAVE TO WAV intent back!"); 1500 if (resultData != null) { 1501 saveToWaveFile(resultData.getData()); 1502 } 1503 break; 1504 1505 case SAVE_TO_PNG_REQUEST: 1506 log("got SAVE TO PNG intent back!"); 1507 if (resultData != null) { 1508 saveScreenShot(resultData.getData()); 1509 } 1510 break; 1511 1512 case SAVE_TO_TXT_REQUEST: 1513 if (resultData != null) { 1514 saveTextToFile(resultData.getData(), getReport().toString()); 1515 } 1516 break; 1517 1518 case SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST: 1519 if (resultData != null) { 1520 int[] bufferPeriodArray = null; 1521 int maxBufferPeriod = Constant.UNKNOWN; 1522 switch (mAudioThreadType) { 1523 case Constant.AUDIO_THREAD_TYPE_JAVA: 1524 bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray(); 1525 maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod(); 1526 break; 1527 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1528 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1529 bufferPeriodArray = mNativeRecorderBufferPeriodArray; 1530 maxBufferPeriod = mNativeRecorderMaxBufferPeriod; 1531 break; 1532 } 1533 saveBufferPeriod(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1534 } 1535 break; 1536 1537 case SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST: 1538 if (resultData != null) { 1539 int[] bufferPeriodArray = null; 1540 int maxBufferPeriod = Constant.UNKNOWN; 1541 switch (mAudioThreadType) { 1542 case Constant.AUDIO_THREAD_TYPE_JAVA: 1543 bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray(); 1544 maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod(); 1545 break; 1546 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1547 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1548 bufferPeriodArray = mNativePlayerBufferPeriodArray; 1549 maxBufferPeriod = mNativePlayerMaxBufferPeriod; 1550 break; 1551 } 1552 saveBufferPeriod(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1553 } 1554 break; 1555 1556 case SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST: 1557 if (resultData != null) { 1558 int[] bufferPeriodArray = null; 1559 int maxBufferPeriod = Constant.UNKNOWN; 1560 switch (mAudioThreadType) { 1561 case Constant.AUDIO_THREAD_TYPE_JAVA: 1562 bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray(); 1563 maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod(); 1564 break; 1565 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1566 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1567 bufferPeriodArray = mNativeRecorderBufferPeriodArray; 1568 maxBufferPeriod = mNativeRecorderMaxBufferPeriod; 1569 break; 1570 } 1571 saveHistogram(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1572 } 1573 break; 1574 1575 case SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST: 1576 if (resultData != null) { 1577 int[] bufferPeriodArray = null; 1578 int maxBufferPeriod = Constant.UNKNOWN; 1579 switch (mAudioThreadType) { 1580 case Constant.AUDIO_THREAD_TYPE_JAVA: 1581 bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray(); 1582 maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod(); 1583 break; 1584 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1585 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1586 bufferPeriodArray = mNativePlayerBufferPeriodArray; 1587 maxBufferPeriod = mNativePlayerMaxBufferPeriod; 1588 break; 1589 } 1590 saveHistogram(resultData.getData(), bufferPeriodArray, maxBufferPeriod); 1591 } 1592 break; 1593 1594 case SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST: 1595 if (resultData != null) { 1596 saveTextToFile(resultData.getData(), 1597 mPlayerCallbackTimes.toString()); 1598 } 1599 break; 1600 1601 case SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST: 1602 if (resultData != null) { 1603 saveTextToFile(resultData.getData(), 1604 mRecorderCallbackTimes.toString()); 1605 } 1606 break; 1607 1608 case SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST: 1609 if (resultData != null) { 1610 saveGlitchOccurrences(resultData.getData(), mGlitchesData); 1611 } 1612 break; 1613 1614 case SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST: 1615 if (resultData != null && mGlitchesData != null && mRecorderCallbackTimes != null 1616 && mPlayerCallbackTimes != null) { 1617 saveHeatMap(resultData.getData(), mRecorderCallbackTimes, mPlayerCallbackTimes, 1618 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize, 1619 mFFTOverlapSamples, mGlitchesData, mSamplingRate), 1620 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, 1621 resultData.getData().toString()); 1622 } 1623 1624 case SETTINGS_ACTIVITY_REQUEST: 1625 log("return from new settings!"); 1626 break; 1627 1628 default: 1629 break; 1630 1631 } 1632 } 1633 } 1634 1635 1636 /** 1637 * Refresh the sound level bar on the main activity to reflect the current sound level 1638 * of the system. 1639 */ refreshSoundLevelBar()1640 private void refreshSoundLevelBar() { 1641 mBarMasterLevel.setEnabled(true); 1642 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 1643 int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC); 1644 mBarMasterLevel.setProgress(currentVolume); 1645 1646 mTextViewCurrentLevel.setText(String.format("Current Sound Level: %d/%d", currentVolume, 1647 mBarMasterLevel.getMax())); 1648 } 1649 1650 1651 /** Reset all results gathered from previous round of test (if any). */ resetResults()1652 private void resetResults() { 1653 mCorrelation.invalidate(); 1654 mNativeRecorderBufferPeriodArray = null; 1655 mNativePlayerBufferPeriodArray = null; 1656 mPlayerCallbackTimes = null; 1657 mRecorderCallbackTimes = null; 1658 mGlitchesData = null; 1659 mRetainedFragment.setWaveData(null); 1660 } 1661 1662 1663 /** Get the file path from uri. Doesn't work for all devices. */ getPath(Uri uri)1664 private String getPath(Uri uri) { 1665 String[] projection = {MediaStore.Images.Media.DATA}; 1666 Cursor cursor1 = getContentResolver().query(uri, projection, null, null, null); 1667 if (cursor1 == null) { 1668 return uri.getPath(); 1669 } 1670 1671 int ColumnIndex = cursor1.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 1672 cursor1.moveToFirst(); 1673 String path = cursor1.getString(ColumnIndex); 1674 cursor1.close(); 1675 return path; 1676 } 1677 1678 1679 /** Zoom out the plot to its full size. */ onButtonZoomOutFull(View view)1680 public void onButtonZoomOutFull(View view) { 1681 double fullZoomOut = mWavePlotView.getMaxZoomOut(); 1682 mWavePlotView.setZoom(fullZoomOut); 1683 mWavePlotView.refreshGraph(); 1684 } 1685 1686 1687 /** Zoom out the plot. */ onButtonZoomOut(View view)1688 public void onButtonZoomOut(View view) { 1689 double zoom = mWavePlotView.getZoom(); 1690 zoom = 2.0 * zoom; 1691 mWavePlotView.setZoom(zoom); 1692 mWavePlotView.refreshGraph(); 1693 } 1694 1695 1696 /** Zoom in the plot. */ onButtonZoomIn(View view)1697 public void onButtonZoomIn(View view) { 1698 double zoom = mWavePlotView.getZoom(); 1699 zoom = zoom / 2.0; 1700 mWavePlotView.setZoom(zoom); 1701 mWavePlotView.refreshGraph(); 1702 } 1703 1704 1705 /** Go to RecorderBufferPeriodActivity */ onButtonRecorderBufferPeriod(View view)1706 public void onButtonRecorderBufferPeriod(View view) { 1707 if (!isBusy()) { 1708 Intent RecorderBufferPeriodIntent = new Intent(this, 1709 RecorderBufferPeriodActivity.class); 1710 int recorderBufferSizeInFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1711 log("recorderBufferSizeInFrames:" + recorderBufferSizeInFrames); 1712 1713 switch (mAudioThreadType) { 1714 case Constant.AUDIO_THREAD_TYPE_JAVA: 1715 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodArray", 1716 mRecorderBufferPeriod.getBufferPeriodArray()); 1717 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodMax", 1718 mRecorderBufferPeriod.getMaxBufferPeriod()); 1719 break; 1720 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1721 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1722 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodArray", 1723 mNativeRecorderBufferPeriodArray); 1724 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodMax", 1725 mNativeRecorderMaxBufferPeriod); 1726 break; 1727 } 1728 1729 RecorderBufferPeriodIntent.putExtra("recorderBufferSize", recorderBufferSizeInFrames); 1730 RecorderBufferPeriodIntent.putExtra("samplingRate", mSamplingRate); 1731 startActivity(RecorderBufferPeriodIntent); 1732 } else 1733 showToast("Test in progress... please wait"); 1734 } 1735 1736 1737 /** Go to PlayerBufferPeriodActivity */ onButtonPlayerBufferPeriod(View view)1738 public void onButtonPlayerBufferPeriod(View view) { 1739 if (!isBusy()) { 1740 Intent PlayerBufferPeriodIntent = new Intent(this, PlayerBufferPeriodActivity.class); 1741 int playerBufferSizeInFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1742 1743 switch (mAudioThreadType) { 1744 case Constant.AUDIO_THREAD_TYPE_JAVA: 1745 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodArray", 1746 mPlayerBufferPeriod.getBufferPeriodArray()); 1747 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodMax", 1748 mPlayerBufferPeriod.getMaxBufferPeriod()); 1749 break; 1750 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1751 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1752 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodArray", 1753 mNativePlayerBufferPeriodArray); 1754 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodMax", 1755 mNativePlayerMaxBufferPeriod); 1756 break; 1757 } 1758 1759 PlayerBufferPeriodIntent.putExtra("playerBufferSize", playerBufferSizeInFrames); 1760 PlayerBufferPeriodIntent.putExtra("samplingRate", mSamplingRate); 1761 startActivity(PlayerBufferPeriodIntent); 1762 } else 1763 showToast("Test in progress... please wait"); 1764 } 1765 1766 1767 /** Display pop up window of recorded glitches */ onButtonGlitches(View view)1768 public void onButtonGlitches(View view) { 1769 if (!isBusy()) { 1770 if (mGlitchesData != null) { 1771 // Create a PopUpWindow with scrollable TextView 1772 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null); 1773 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 1774 ViewGroup.LayoutParams.MATCH_PARENT, true); 1775 1776 // Generate report of glitch intervals and set pop up window text 1777 TextView GlitchText = 1778 (TextView) popUp.getContentView().findViewById(R.id.ReportInfo); 1779 GlitchText.setText(GlitchesStringBuilder.getGlitchString(mFFTSamplingSize, 1780 mFFTOverlapSamples, mGlitchesData, mSamplingRate, 1781 mGlitchingIntervalTooLong, estimateNumberOfGlitches(mGlitchesData))); 1782 1783 // display pop up window, dismissible with back button 1784 popUp.showAtLocation((View) findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0); 1785 } else { 1786 showToastImportant("Please run the buffer test to get data"); 1787 } 1788 1789 } else { 1790 showToast("Test in progress... please wait"); 1791 } 1792 } 1793 1794 /** Display pop up window of recorded metrics and system information */ onButtonReport(View view)1795 public void onButtonReport(View view) { 1796 if (!isBusy()) { 1797 if ((mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD 1798 && mGlitchesData != null) 1799 || (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY 1800 && mCorrelation.isValid())) { 1801 // Create a PopUpWindow with scrollable TextView 1802 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null); 1803 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 1804 ViewGroup.LayoutParams.MATCH_PARENT, true); 1805 1806 // Generate report of glitch intervals and set pop up window text 1807 TextView reportText = 1808 (TextView) popUp.getContentView().findViewById(R.id.ReportInfo); 1809 reportText.setText(getReport().toString()); 1810 1811 // display pop up window, dismissible with back button 1812 popUp.showAtLocation((View) findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0); 1813 } else { 1814 showToastImportant("Please run the tests to get data"); 1815 } 1816 1817 } else { 1818 showToast("Test in progress... please wait"); 1819 } 1820 } 1821 1822 /** Display pop up window of recorded metrics and system information */ onButtonHeatMap(View view)1823 public void onButtonHeatMap(View view) { 1824 if (!isBusy()) { 1825 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD 1826 && mGlitchesData != null && mRecorderCallbackTimes != null 1827 && mRecorderCallbackTimes != null) { 1828 1829 // Create a PopUpWindow with heatMap custom view 1830 View puLayout = this.getLayoutInflater().inflate(R.layout.heatmap_window, null); 1831 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 1832 ViewGroup.LayoutParams.MATCH_PARENT, true); 1833 1834 ((LinearLayout) popUp.getContentView()).addView( 1835 new GlitchAndCallbackHeatMapView(this, mRecorderCallbackTimes, 1836 mPlayerCallbackTimes, 1837 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize, 1838 mFFTOverlapSamples, mGlitchesData, mSamplingRate), 1839 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, 1840 getResources().getString(R.string.heatTitle))); 1841 1842 popUp.showAtLocation((View) findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0); 1843 1844 } else { 1845 showToastImportant("Please run the tests to get data"); 1846 } 1847 1848 } else { 1849 showToast("Test in progress... please wait"); 1850 } 1851 } 1852 1853 /** Redraw the plot according to mWaveData */ refreshPlots()1854 private void refreshPlots() { 1855 mWavePlotView.setData(mRetainedFragment.getWaveData(), mSamplingRate); 1856 mWavePlotView.redraw(); 1857 } 1858 audioThreadTypeToString(int audioThreadType)1859 static String audioThreadTypeToString(int audioThreadType) { 1860 switch (audioThreadType) { 1861 case Constant.AUDIO_THREAD_TYPE_JAVA: return "JAVA"; 1862 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: return "NATIVE (SLES)"; 1863 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: return "NATIVE (AAUDIO)"; 1864 } 1865 return "DEFAULT"; 1866 } 1867 1868 /** Refresh the text on the main activity that shows the app states and audio settings. */ refreshState()1869 private void refreshState() { 1870 log("refreshState!"); 1871 refreshSoundLevelBar(); 1872 1873 // get info 1874 int playerFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1875 int recorderFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME; 1876 StringBuilder s = new StringBuilder(200); 1877 1878 s.append("Settings from most recent run (at "); 1879 s.append(mTestStartTimeString); 1880 s.append("):\n"); 1881 1882 s.append("SR: ").append(mSamplingRate).append(" Hz"); 1883 s.append(" ChannelIndex: ").append(mChannelIndex < 0 ? "MONO" : mChannelIndex); 1884 switch (mAudioThreadType) { 1885 case Constant.AUDIO_THREAD_TYPE_JAVA: 1886 s.append(" Play Frames: " ).append(playerFrames); 1887 s.append(" Record Frames: ").append(recorderFrames); 1888 break; 1889 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 1890 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 1891 s.append(" Frames: ").append(playerFrames); 1892 break; 1893 } 1894 s.append(" Audio: ").append(audioThreadTypeToString(mAudioThreadType)); 1895 1896 // mic source 1897 String micSourceName = getApp().getMicSourceString(mMicSource); 1898 if (micSourceName != null) { 1899 s.append(" Mic: ").append(micSourceName); 1900 } 1901 1902 // performance mode 1903 String performanceModeName = getApp().getPerformanceModeString(mPerformanceMode); 1904 if (performanceModeName != null) { 1905 s.append(" Performance Mode: ").append(performanceModeName); 1906 } 1907 1908 // sound level at start of test 1909 s.append(" Sound Level: ").append(mSoundLevel).append("/").append(mBarMasterLevel.getMax()); 1910 1911 // load threads 1912 s.append(" Simulated Load Threads: ").append(getApp().getNumberOfLoadThreads()); 1913 1914 // Show short summary of results, round trip latency or number of glitches 1915 if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY) { 1916 if (mIgnoreFirstFrames > 0) { 1917 s.append(" First Frames Ignored: ").append(mIgnoreFirstFrames); 1918 } 1919 if (mCorrelation.isValid()) { 1920 mTextViewResultSummary.setText(String.format("Latency: %.2f ms Confidence: %.2f" + 1921 " Average = %.4f RMS = %.4f", 1922 mCorrelation.mEstimatedLatencyMs, mCorrelation.mEstimatedLatencyConfidence, 1923 mCorrelation.mAverage, mCorrelation.mRms)); 1924 } 1925 } else if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD && 1926 mGlitchesData != null) { 1927 // show buffer test duration 1928 s.append("\nBuffer Test Duration: ").append(mBufferTestDurationInSeconds).append(" s"); 1929 1930 // show buffer test wave plot duration 1931 s.append(" Buffer Test Wave Plot Duration: last "); 1932 s.append(mBufferTestWavePlotDurationInSeconds); 1933 s.append(" s"); 1934 1935 mTextViewResultSummary.setText(getResources().getString(R.string.numGlitches) + " " + 1936 estimateNumberOfGlitches(mGlitchesData)); 1937 } else { 1938 mTextViewResultSummary.setText(""); 1939 } 1940 1941 String info = getApp().getSystemInfo(); 1942 s.append(" ").append(info); 1943 1944 mTextInfo.setText(s.toString()); 1945 } 1946 1947 log(String msg)1948 private static void log(String msg) { 1949 Log.v(TAG, msg); 1950 } 1951 showToast(final String msg)1952 public void showToast(final String msg) { 1953 /* doShowToast(msg, false); */ 1954 } 1955 showToastImportant(final String msg)1956 public void showToastImportant(final String msg) { 1957 /* doShowToast(msg, true); */ 1958 } 1959 doShowToast(final String msg, boolean isImportant)1960 private void doShowToast(final String msg, boolean isImportant) { 1961 // If launched from an intent, do not show unimportant toasts. 1962 // Intents are typically used by scripts, which run actions at high 1963 // rate. On some versions of Android this causes the toasts to 1964 // queue up, and they continue displaying long after the app has 1965 // finished the actions. 1966 if (mIntentRunning && !isImportant) { 1967 return; 1968 } 1969 // Make sure UI manipulations are only done on the UI thread 1970 runOnUiThread(new Runnable() { 1971 public void run() { 1972 Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT); 1973 toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 10, 10); 1974 toast.show(); 1975 } 1976 }); 1977 } 1978 1979 1980 /** Get the application that runs this activity. Wrapper for getApplication(). */ getApp()1981 private LoopbackApplication getApp() { 1982 return (LoopbackApplication) this.getApplication(); 1983 } 1984 1985 1986 /** Save a .wav file of the wave plot on the main activity. */ saveToWaveFile(Uri uri)1987 private void saveToWaveFile(Uri uri) { 1988 double[] waveData = mRetainedFragment.getWaveData(); 1989 if (waveData != null && waveData.length > 0) { 1990 AudioFileOutput audioFileOutput = new AudioFileOutput(getApplicationContext(), uri, 1991 mSamplingRate); 1992 boolean status = audioFileOutput.writeData(waveData); 1993 if (status) { 1994 String wavFileAbsolutePath = getPath(uri); 1995 // for some devices getPath fails 1996 if (wavFileAbsolutePath != null) { 1997 File file = new File(wavFileAbsolutePath); 1998 wavFileAbsolutePath = file.getAbsolutePath(); 1999 } else { 2000 wavFileAbsolutePath = ""; 2001 } 2002 showToast("Finished exporting wave File " + wavFileAbsolutePath); 2003 } else { 2004 showToastImportant("Something failed saving wave file"); 2005 } 2006 2007 } 2008 } 2009 2010 2011 /** Save a screenshot of the main activity. */ saveScreenShot(Uri uri)2012 private void saveScreenShot(Uri uri) { 2013 ParcelFileDescriptor parcelFileDescriptor = null; 2014 FileOutputStream outputStream; 2015 try { 2016 parcelFileDescriptor = getApplicationContext().getContentResolver(). 2017 openFileDescriptor(uri, "w"); 2018 2019 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 2020 outputStream = new FileOutputStream(fileDescriptor); 2021 2022 log("Done creating output stream"); 2023 2024 LinearLayout LL = (LinearLayout) findViewById(R.id.linearLayoutMain); 2025 2026 View v = LL.getRootView(); 2027 v.setDrawingCacheEnabled(true); 2028 Bitmap b = v.getDrawingCache(); 2029 2030 //save 2031 b.compress(Bitmap.CompressFormat.PNG, 100, outputStream); 2032 parcelFileDescriptor.close(); 2033 v.setDrawingCacheEnabled(false); 2034 } catch (Exception e) { 2035 log("Failed to open png file " + e); 2036 } finally { 2037 try { 2038 if (parcelFileDescriptor != null) { 2039 parcelFileDescriptor.close(); 2040 } 2041 } catch (Exception e) { 2042 e.printStackTrace(); 2043 log("Error closing ParcelFile Descriptor"); 2044 } 2045 } 2046 } 2047 saveHistogram(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod)2048 private void saveHistogram(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod) { 2049 // Create and histogram view bitmap 2050 HistogramView recordHisto = new HistogramView(this,null); 2051 recordHisto.setBufferPeriodArray(bufferPeriodArray); 2052 recordHisto.setMaxBufferPeriod(maxBufferPeriod); 2053 2054 // Draw histogram on bitmap canvas 2055 Bitmap histoBmp = Bitmap.createBitmap(HISTOGRAM_EXPORT_WIDTH, 2056 HISTOGRAM_EXPORT_HEIGHT, Bitmap.Config.ARGB_8888); // creates a MUTABLE bitmap 2057 recordHisto.fillCanvas(new Canvas(histoBmp), histoBmp.getWidth(), histoBmp.getHeight()); 2058 2059 saveImage(uri, histoBmp); 2060 } 2061 saveHeatMap(Uri uri, BufferCallbackTimes recorderCallbackTimes, BufferCallbackTimes playerCallbackTimes, int[] glitchMilliseconds, boolean glitchesExceeded, int duration, String title)2062 private void saveHeatMap(Uri uri, BufferCallbackTimes recorderCallbackTimes, 2063 BufferCallbackTimes playerCallbackTimes, int[] glitchMilliseconds, 2064 boolean glitchesExceeded, int duration, String title) { 2065 Bitmap heatBmp = Bitmap.createBitmap(HEATMAP_DRAW_WIDTH, HEATMAP_DRAW_HEIGHT, 2066 Bitmap.Config.ARGB_8888); 2067 GlitchAndCallbackHeatMapView.fillCanvas(new Canvas(heatBmp), recorderCallbackTimes, 2068 playerCallbackTimes, glitchMilliseconds, glitchesExceeded, duration, 2069 title); 2070 saveImage(uri, Bitmap.createScaledBitmap(heatBmp, 2071 HEATMAP_DRAW_WIDTH / HEATMAP_EXPORT_DIVISOR, 2072 HEATMAP_DRAW_HEIGHT / HEATMAP_EXPORT_DIVISOR, false)); 2073 } 2074 2075 /** Save an image to file. */ saveImage(Uri uri, Bitmap bmp)2076 private void saveImage(Uri uri, Bitmap bmp) { 2077 ParcelFileDescriptor parcelFileDescriptor = null; 2078 FileOutputStream outputStream; 2079 try { 2080 parcelFileDescriptor = getApplicationContext().getContentResolver(). 2081 openFileDescriptor(uri, "w"); 2082 2083 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 2084 outputStream = new FileOutputStream(fileDescriptor); 2085 2086 log("Done creating output stream"); 2087 2088 // Save compressed bitmap to file 2089 bmp.compress(Bitmap.CompressFormat.PNG, EXPORTED_IMAGE_QUALITY, outputStream); 2090 parcelFileDescriptor.close(); 2091 } catch (Exception e) { 2092 log("Failed to open png file " + e); 2093 } finally { 2094 try { 2095 if (parcelFileDescriptor != null) { 2096 parcelFileDescriptor.close(); 2097 } 2098 } catch (Exception e) { 2099 e.printStackTrace(); 2100 log("Error closing ParcelFile Descriptor"); 2101 } 2102 } 2103 } 2104 2105 2106 /** 2107 * Save a .txt file of the given buffer period's data. 2108 * First column is time, second column is count. 2109 */ saveBufferPeriod(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod)2110 private void saveBufferPeriod(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod) { 2111 ParcelFileDescriptor parcelFileDescriptor = null; 2112 FileOutputStream outputStream; 2113 if (bufferPeriodArray != null) { 2114 try { 2115 parcelFileDescriptor = getApplicationContext().getContentResolver(). 2116 openFileDescriptor(uri, "w"); 2117 2118 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 2119 outputStream = new FileOutputStream(fileDescriptor); 2120 log("Done creating output stream for saving buffer period"); 2121 2122 int usefulDataRange = Math.min(maxBufferPeriod + 1, bufferPeriodArray.length); 2123 int[] usefulBufferData = Arrays.copyOfRange(bufferPeriodArray, 0, usefulDataRange); 2124 2125 String endline = "\n"; 2126 String delimiter = ","; 2127 StringBuilder sb = new StringBuilder(); 2128 for (int i = 0; i < usefulBufferData.length; i++) { 2129 sb.append(i + delimiter + usefulBufferData[i] + endline); 2130 } 2131 2132 outputStream.write(sb.toString().getBytes()); 2133 2134 } catch (Exception e) { 2135 log("Failed to open text file " + e); 2136 } finally { 2137 try { 2138 if (parcelFileDescriptor != null) { 2139 parcelFileDescriptor.close(); 2140 } 2141 } catch (Exception e) { 2142 e.printStackTrace(); 2143 log("Error closing ParcelFile Descriptor"); 2144 } 2145 } 2146 } 2147 2148 } 2149 2150 /** Save a .txt file of various test results. */ saveTextToFile(Uri uri, String outputText)2151 private void saveTextToFile(Uri uri, String outputText) { 2152 ParcelFileDescriptor parcelFileDescriptor = null; 2153 FileOutputStream outputStream; 2154 try { 2155 parcelFileDescriptor = getApplicationContext().getContentResolver(). 2156 openFileDescriptor(uri, "w"); 2157 2158 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 2159 outputStream = new FileOutputStream(fileDescriptor); 2160 log("Done creating output stream"); 2161 2162 outputStream.write(outputText.getBytes()); 2163 parcelFileDescriptor.close(); 2164 } catch (Exception e) { 2165 log("Failed to open text file " + e); 2166 } finally { 2167 try { 2168 if (parcelFileDescriptor != null) { 2169 parcelFileDescriptor.close(); 2170 } 2171 } catch (Exception e) { 2172 e.printStackTrace(); 2173 log("Error closing ParcelFile Descriptor"); 2174 } 2175 } 2176 } 2177 getReport()2178 private StringBuilder getReport() { 2179 String endline = "\n"; 2180 final int stringLength = 300; 2181 StringBuilder sb = new StringBuilder(stringLength); 2182 sb.append("DateTime = " + mTestStartTimeString + endline); 2183 sb.append(INTENT_SAMPLING_FREQUENCY + " = " + mSamplingRate + endline); 2184 sb.append(INTENT_CHANNEL_INDEX + " = " + mChannelIndex + endline); 2185 sb.append(INTENT_RECORDER_BUFFER + " = " + mRecorderBufferSizeInBytes / 2186 Constant.BYTES_PER_FRAME + endline); 2187 sb.append(INTENT_PLAYER_BUFFER + " = " + mPlayerBufferSizeInBytes / 2188 Constant.BYTES_PER_FRAME + endline); 2189 sb.append(INTENT_AUDIO_THREAD + " = " + mAudioThreadType + endline); 2190 2191 String audioType = audioThreadTypeToString(mAudioThreadType); 2192 sb.append(INTENT_AUDIO_THREAD + "_String = " + audioType + endline); 2193 2194 sb.append(INTENT_MIC_SOURCE + " = " + mMicSource + endline); 2195 sb.append(INTENT_MIC_SOURCE + "_String = " + getApp().getMicSourceString(mMicSource) 2196 + endline); 2197 sb.append(INTENT_AUDIO_LEVEL + " = " + mSoundLevel + endline); 2198 2199 switch (mTestType) { 2200 2201 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY: 2202 sb.append(INTENT_IGNORE_FIRST_FRAMES + " = " + mIgnoreFirstFrames + endline); 2203 if (mCorrelation.isValid()) { 2204 sb.append(String.format("LatencyMs = %.2f", mCorrelation.mEstimatedLatencyMs) 2205 + endline); 2206 } else { 2207 sb.append(String.format("LatencyMs = unknown") + endline); 2208 } 2209 2210 sb.append(String.format("LatencyConfidence = %.2f", 2211 mCorrelation.mEstimatedLatencyConfidence) + endline); 2212 2213 sb.append(String.format("Average = %.4f", mCorrelation.mAverage) + endline); 2214 sb.append(String.format("RMS = %.4f", mCorrelation.mRms) + endline); 2215 break; 2216 2217 case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD: 2218 sb.append("Buffer Test Duration (s) = " + mBufferTestDurationInSeconds + endline); 2219 2220 // report recorder results 2221 int[] recorderBufferData = null; 2222 int recorderBufferDataMax = 0; 2223 double recorderBufferDataStdDev = 0.0; 2224 switch (mAudioThreadType) { 2225 case Constant.AUDIO_THREAD_TYPE_JAVA: 2226 recorderBufferData = mRecorderBufferPeriod.getBufferPeriodArray(); 2227 recorderBufferDataMax = mRecorderBufferPeriod.getMaxBufferPeriod(); 2228 recorderBufferDataStdDev = mRecorderBufferPeriod.getStdDevBufferPeriod(); 2229 break; 2230 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 2231 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 2232 recorderBufferData = mNativeRecorderBufferPeriodArray; 2233 recorderBufferDataMax = mNativeRecorderMaxBufferPeriod; 2234 recorderBufferDataStdDev = mNativeRecorderStdDevBufferPeriod; 2235 break; 2236 } 2237 // report expected recorder buffer period 2238 if (recorderBufferData != null) { 2239 // this is the range of data that actually has values 2240 int usefulDataRange = Math.min(recorderBufferDataMax + 1, 2241 recorderBufferData.length); 2242 int[] usefulBufferData = Arrays.copyOfRange(recorderBufferData, 0, 2243 usefulDataRange); 2244 PerformanceMeasurement measurement = new PerformanceMeasurement( 2245 mRecorderCallbackTimes.getExpectedBufferPeriod(), usefulBufferData); 2246 double recorderPercentAtExpected = 2247 measurement.percentBufferPeriodsAtExpected(); 2248 double benchmark = measurement.computeWeightedBenchmark(); 2249 int outliers = measurement.countOutliers(); 2250 sb.append("Expected Recorder Buffer Period (ms) = " + 2251 mRecorderCallbackTimes.getExpectedBufferPeriod() + endline); 2252 sb.append("Recorder Buffer Periods At Expected = " + 2253 String.format("%.5f%%", recorderPercentAtExpected * 100) + endline); 2254 2255 sb.append("Recorder Buffer Period Std Dev = " 2256 + String.format(Locale.US, "%.5f ms", recorderBufferDataStdDev) 2257 + endline); 2258 2259 // output thousandths of a percent not at expected buffer period 2260 sb.append("kth% Late Recorder Buffer Callbacks = " 2261 + String.format("%.5f", (1 - recorderPercentAtExpected) * 100000) 2262 + endline); 2263 sb.append("Recorder Benchmark = " + benchmark + endline); 2264 sb.append("Recorder Number of Outliers = " + outliers + endline); 2265 } else { 2266 sb.append("Cannot Find Recorder Buffer Period Data!" + endline); 2267 } 2268 2269 // report player results 2270 int[] playerBufferData = null; 2271 int playerBufferDataMax = 0; 2272 double playerBufferDataStdDev = 0.0; 2273 switch (mAudioThreadType) { 2274 case Constant.AUDIO_THREAD_TYPE_JAVA: 2275 playerBufferData = mPlayerBufferPeriod.getBufferPeriodArray(); 2276 playerBufferDataMax = mPlayerBufferPeriod.getMaxBufferPeriod(); 2277 playerBufferDataStdDev = mPlayerBufferPeriod.getStdDevBufferPeriod(); 2278 break; 2279 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 2280 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 2281 playerBufferData = mNativePlayerBufferPeriodArray; 2282 playerBufferDataMax = mNativePlayerMaxBufferPeriod; 2283 playerBufferDataStdDev = mNativePlayerStdDevBufferPeriod; 2284 break; 2285 } 2286 // report expected player buffer period 2287 sb.append("Expected Player Buffer Period (ms) = " + 2288 mPlayerCallbackTimes.getExpectedBufferPeriod() + endline); 2289 if (playerBufferData != null) { 2290 // this is the range of data that actually has values 2291 int usefulDataRange = Math.min(playerBufferDataMax + 1, 2292 playerBufferData.length); 2293 int[] usefulBufferData = Arrays.copyOfRange(playerBufferData, 0, 2294 usefulDataRange); 2295 PerformanceMeasurement measurement = new PerformanceMeasurement( 2296 mPlayerCallbackTimes.getExpectedBufferPeriod(), usefulBufferData); 2297 double playerPercentAtExpected = measurement.percentBufferPeriodsAtExpected(); 2298 double benchmark = measurement.computeWeightedBenchmark(); 2299 int outliers = measurement.countOutliers(); 2300 sb.append("Player Buffer Periods At Expected = " 2301 + String.format("%.5f%%", playerPercentAtExpected * 100) + endline); 2302 2303 sb.append("Player Buffer Period Std Dev = " 2304 + String.format(Locale.US, "%.5f ms", playerBufferDataStdDev) 2305 + endline); 2306 2307 // output thousandths of a percent not at expected buffer period 2308 sb.append("kth% Late Player Buffer Callbacks = " 2309 + String.format("%.5f", (1 - playerPercentAtExpected) * 100000) 2310 + endline); 2311 sb.append("Player Benchmark = " + benchmark + endline); 2312 sb.append("Player Number of Outliers = " + outliers + endline); 2313 2314 } else { 2315 sb.append("Cannot Find Player Buffer Period Data!" + endline); 2316 } 2317 // report glitches per hour 2318 int numberOfGlitches = estimateNumberOfGlitches(mGlitchesData); 2319 double testDurationInHours = mBufferTestElapsedSeconds 2320 / (double) Constant.SECONDS_PER_HOUR; 2321 2322 // Report Glitches Per Hour if sufficient data available, ie at least half an hour 2323 if (testDurationInHours >= .5) { 2324 int glitchesPerHour = (int) Math.ceil(numberOfGlitches/testDurationInHours); 2325 sb.append("Glitches Per Hour = " + glitchesPerHour + endline); 2326 } 2327 sb.append("Total Number of Glitches = " + numberOfGlitches + endline); 2328 2329 // report if the total glitching interval is too long 2330 sb.append("Total glitching interval too long = " + 2331 mGlitchingIntervalTooLong); 2332 2333 sb.append("\nLate Player Callbacks = "); 2334 sb.append(mPlayerCallbackTimes.getNumLateOrEarlyCallbacks()); 2335 sb.append("\nLate Player Callbacks Exceeded Capacity = "); 2336 sb.append(mPlayerCallbackTimes.isCapacityExceeded()); 2337 sb.append("\nLate Recorder Callbacks = "); 2338 sb.append(mRecorderCallbackTimes.getNumLateOrEarlyCallbacks()); 2339 sb.append("\nLate Recorder Callbacks Exceeded Capacity = "); 2340 sb.append(mRecorderCallbackTimes.isCapacityExceeded()); 2341 sb.append("\n"); 2342 } 2343 2344 2345 String info = getApp().getSystemInfo(); 2346 sb.append("SystemInfo = " + info + endline); 2347 2348 return sb; 2349 } 2350 2351 /** Save a .txt file of of glitch occurrences in ms from beginning of test. */ saveGlitchOccurrences(Uri uri, int[] glitchesData)2352 private void saveGlitchOccurrences(Uri uri, int[] glitchesData) { 2353 ParcelFileDescriptor parcelFileDescriptor = null; 2354 FileOutputStream outputStream; 2355 try { 2356 parcelFileDescriptor = getApplicationContext().getContentResolver(). 2357 openFileDescriptor(uri, "w"); 2358 2359 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 2360 outputStream = new FileOutputStream(fileDescriptor); 2361 2362 log("Done creating output stream"); 2363 2364 outputStream.write(GlitchesStringBuilder.getGlitchStringForFile(mFFTSamplingSize, 2365 mFFTOverlapSamples, glitchesData, mSamplingRate).getBytes()); 2366 } catch (Exception e) { 2367 log("Failed to open text file " + e); 2368 } finally { 2369 try { 2370 if (parcelFileDescriptor != null) { 2371 parcelFileDescriptor.close(); 2372 } 2373 } catch (Exception e) { 2374 e.printStackTrace(); 2375 log("Error closing ParcelFile Descriptor"); 2376 } 2377 } 2378 } 2379 2380 /** 2381 * Estimate the number of glitches. This version of estimation will count two consecutive 2382 * glitching intervals as one glitch. This is because two time intervals are partly overlapped. 2383 * Note: If the total glitching intervals exceed the length of glitchesData, this estimation 2384 * becomes incomplete. However, whether or not the total glitching interval is too long will 2385 * also be indicated, and in the case it's true, we know something went wrong. 2386 */ estimateNumberOfGlitches(int[] glitchesData)2387 private static int estimateNumberOfGlitches(int[] glitchesData) { 2388 final int discard = 10; // don't count glitches occurring at the first few FFT interval 2389 boolean isPreviousGlitch = false; // is there a glitch in previous interval or not 2390 int previousFFTInterval = -1; 2391 int count = 0; 2392 // if there are three consecutive glitches, the first two will be counted as one, 2393 // the third will be counted as another one 2394 for (int i = 0; i < glitchesData.length; i++) { 2395 if (glitchesData[i] > discard) { 2396 if (glitchesData[i] == previousFFTInterval + 1 && isPreviousGlitch) { 2397 isPreviousGlitch = false; 2398 previousFFTInterval = glitchesData[i]; 2399 } else { 2400 isPreviousGlitch = true; 2401 previousFFTInterval = glitchesData[i]; 2402 count += 1; 2403 } 2404 } 2405 2406 } 2407 2408 return count; 2409 } 2410 2411 2412 /** 2413 * Estimate the number of glitches. This version of estimation will count the whole consecutive 2414 * intervals as one glitch. This version is not currently used. 2415 * Note: If the total glitching intervals exceed the length of glitchesData, this estimation 2416 * becomes incomplete. However, whether or not the total glitching interval is too long will 2417 * also be indicated, and in the case it's true, we know something went wrong. 2418 */ estimateNumberOfGlitches2(int[] glitchesData)2419 private static int estimateNumberOfGlitches2(int[] glitchesData) { 2420 final int discard = 10; // don't count glitches occurring at the first few FFT interval 2421 int previousFFTInterval = -1; 2422 int count = 0; 2423 for (int i = 0; i < glitchesData.length; i++) { 2424 if (glitchesData[i] > discard) { 2425 if (glitchesData[i] != previousFFTInterval + 1) { 2426 count += 1; 2427 } 2428 previousFFTInterval = glitchesData[i]; 2429 } 2430 } 2431 return count; 2432 } 2433 2434 /** 2435 * Check whether we have the RECORD_AUDIO permission 2436 * @return true if we do 2437 */ hasRecordAudioPermission()2438 private boolean hasRecordAudioPermission() { 2439 boolean hasPermission = (ContextCompat.checkSelfPermission(this, 2440 Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED); 2441 2442 log("Has RECORD_AUDIO permission? " + hasPermission); 2443 return hasPermission; 2444 } 2445 2446 /** 2447 * Requests the RECORD_AUDIO permission from the user 2448 */ requestRecordAudioPermission(int requestCode)2449 private void requestRecordAudioPermission(int requestCode) { 2450 2451 String requiredPermission = Manifest.permission.RECORD_AUDIO; 2452 2453 // If the user previously denied this permission then show a message explaining why 2454 // this permission is needed 2455 if (ActivityCompat.shouldShowRequestPermissionRationale(this, 2456 requiredPermission)) { 2457 2458 showToastImportant("This app needs to record audio through the microphone to test "+ 2459 "the device's performance"); 2460 } 2461 2462 // request the permission. 2463 ActivityCompat.requestPermissions(this, new String[]{requiredPermission}, requestCode); 2464 } 2465 2466 @Override onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)2467 public void onRequestPermissionsResult(int requestCode, 2468 String permissions[], int[] grantResults) { 2469 2470 // Save all files or run requested test after being granted permissions 2471 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 2472 if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS ) { 2473 saveAllTo(getFileNamePrefix()); 2474 } else if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT ) { 2475 AtraceScriptsWriter.writeScriptsToFile(this); 2476 } else if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER) { 2477 startBufferTest(); 2478 } else if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY) { 2479 startLatencyTest(); 2480 } 2481 } 2482 } 2483 2484 /** 2485 * Check whether we have the WRITE_EXTERNAL_STORAGE permission 2486 * 2487 * @return true if we do 2488 */ hasWriteFilePermission()2489 private boolean hasWriteFilePermission() { 2490 boolean hasPermission = (ContextCompat.checkSelfPermission(this, 2491 Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); 2492 2493 log("Has WRITE_EXTERNAL_STORAGE? " + hasPermission); 2494 return hasPermission; 2495 } 2496 2497 /** 2498 * Requests the WRITE_EXTERNAL_STORAGE permission from the user 2499 */ requestWriteFilePermission(int requestCode)2500 private void requestWriteFilePermission(int requestCode) { 2501 2502 String requiredPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE; 2503 2504 // request the permission. 2505 ActivityCompat.requestPermissions(this, new String[]{requiredPermission}, requestCode); 2506 } 2507 2508 /** 2509 * Receive results from save files DialogAlert and either save all files directly 2510 * or use filename dialog 2511 */ 2512 @Override onSaveDialogSelect(DialogFragment dialog, boolean saveWithoutDialog)2513 public void onSaveDialogSelect(DialogFragment dialog, boolean saveWithoutDialog) { 2514 if (saveWithoutDialog) { 2515 saveAllTo("loopback_" + mTestStartTimeString); 2516 } else { 2517 SaveFilesWithDialog(); 2518 } 2519 } 2520 restoreInstanceState(Bundle in)2521 private void restoreInstanceState(Bundle in) { 2522 mTestType = in.getInt("mTestType"); 2523 mMicSource = in.getInt("mMicSource"); 2524 mAudioThreadType = in.getInt("mAudioThreadType"); 2525 mSamplingRate = in.getInt("mSamplingRate"); 2526 mChannelIndex = in.getInt("mChannelIndex"); 2527 mSoundLevel = in.getInt("mSoundLevel"); 2528 mPlayerBufferSizeInBytes = in.getInt("mPlayerBufferSizeInBytes"); 2529 mRecorderBufferSizeInBytes = in.getInt("mRecorderBufferSizeInBytes"); 2530 2531 mTestStartTimeString = in.getString("mTestStartTimeString"); 2532 2533 mGlitchesData = in.getIntArray("mGlitchesData"); 2534 if (mGlitchesData != null) { 2535 mGlitchingIntervalTooLong = in.getBoolean("mGlitchingIntervalTooLong"); 2536 mFFTSamplingSize = in.getInt("mFFTSamplingSize"); 2537 mFFTOverlapSamples = in.getInt("mFFTOverlapSamples"); 2538 mBufferTestStartTime = in.getLong("mBufferTestStartTime"); 2539 mBufferTestElapsedSeconds = in.getInt("mBufferTestElapsedSeconds"); 2540 mBufferTestDurationInSeconds = in.getInt("mBufferTestDurationInSeconds"); 2541 mBufferTestWavePlotDurationInSeconds = 2542 in.getInt("mBufferTestWavePlotDurationInSeconds"); 2543 2544 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE); 2545 } 2546 2547 if (mRetainedFragment.getWaveData() != null) { 2548 mCorrelation = in.getParcelable("mCorrelation"); 2549 mPlayerBufferPeriod = in.getParcelable("mPlayerBufferPeriod"); 2550 mRecorderBufferPeriod = in.getParcelable("mRecorderBufferPeriod"); 2551 mPlayerCallbackTimes = in.getParcelable("mPlayerCallbackTimes"); 2552 mRecorderCallbackTimes = in.getParcelable("mRecorderCallbackTimes"); 2553 2554 mNativePlayerBufferPeriodArray = in.getIntArray("mNativePlayerBufferPeriodArray"); 2555 mNativePlayerMaxBufferPeriod = in.getInt("mNativePlayerMaxBufferPeriod"); 2556 mNativeRecorderBufferPeriodArray = in.getIntArray("mNativeRecorderBufferPeriodArray"); 2557 mNativeRecorderMaxBufferPeriod = in.getInt("mNativeRecorderMaxBufferPeriod"); 2558 2559 mWavePlotView.setData(mRetainedFragment.getWaveData(), mSamplingRate); 2560 refreshState(); 2561 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE); 2562 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE); 2563 } 2564 } 2565 2566 @Override onSaveInstanceState(Bundle out)2567 protected void onSaveInstanceState(Bundle out) { 2568 super.onSaveInstanceState(out); 2569 2570 out.putInt("mTestType", mTestType); 2571 out.putInt("mMicSource", mMicSource); 2572 out.putInt("mAudioThreadType", mAudioThreadType); 2573 out.putInt("mSamplingRate", mSamplingRate); 2574 out.putInt("mChannelIndex", mChannelIndex); 2575 out.putInt("mSoundLevel", mSoundLevel); 2576 out.putInt("mPlayerBufferSizeInBytes", mPlayerBufferSizeInBytes); 2577 out.putInt("mRecorderBufferSizeInBytes", mRecorderBufferSizeInBytes); 2578 out.putString("mTestStartTimeString", mTestStartTimeString); 2579 2580 out.putParcelable("mCorrelation", mCorrelation); 2581 out.putParcelable("mPlayerBufferPeriod", mPlayerBufferPeriod); 2582 out.putParcelable("mRecorderBufferPeriod", mRecorderBufferPeriod); 2583 out.putParcelable("mPlayerCallbackTimes", mPlayerCallbackTimes); 2584 out.putParcelable("mRecorderCallbackTimes", mRecorderCallbackTimes); 2585 2586 out.putIntArray("mNativePlayerBufferPeriodArray", mNativePlayerBufferPeriodArray); 2587 out.putInt("mNativePlayerMaxBufferPeriod", mNativePlayerMaxBufferPeriod); 2588 out.putIntArray("mNativeRecorderBufferPeriodArray", mNativeRecorderBufferPeriodArray); 2589 out.putInt("mNativeRecorderMaxBufferPeriod", mNativeRecorderMaxBufferPeriod); 2590 2591 // buffer test values 2592 out.putIntArray("mGlitchesData", mGlitchesData); 2593 out.putBoolean("mGlitchingIntervalTooLong", mGlitchingIntervalTooLong); 2594 out.putInt("mFFTSamplingSize", mFFTSamplingSize); 2595 out.putInt("mFFTOverlapSamples", mFFTOverlapSamples); 2596 out.putLong("mBufferTestStartTime", mBufferTestStartTime); 2597 out.putInt("mBufferTestElapsedSeconds", mBufferTestElapsedSeconds); 2598 out.putInt("mBufferTestDurationInSeconds", mBufferTestDurationInSeconds); 2599 out.putInt("mBufferTestWavePlotDurationInSeconds", mBufferTestWavePlotDurationInSeconds); 2600 } 2601 waitForUsbRoute()2602 private void waitForUsbRoute() { 2603 log("Start checking for USB Route connection"); 2604 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 2605 long startTime = System.currentTimeMillis(); 2606 int iter = 0; 2607 while (true) { 2608 if (System.currentTimeMillis() - startTime > 15 * 1000) { 2609 log("15 Seconds has elapsed before USB_AUDIO_ROUTE is detected, continue test."); 2610 break; 2611 } 2612 iter++; 2613 AudioDeviceInfo[] devices; 2614 boolean usb_available = false; 2615 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 2616 devices = am.getDevices(AudioManager.GET_DEVICES_INPUTS); 2617 for (AudioDeviceInfo devInfo : devices) { 2618 2619 if (devInfo.getType() != AudioDeviceInfo.TYPE_BUILTIN_MIC && devInfo.getType() != AudioDeviceInfo.TYPE_TELEPHONY) { 2620 log(" USB Check iteration: " + String.valueOf(iter)); 2621 log(" USB Check get type: " + String.valueOf(devInfo.getType())); 2622 } 2623 if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || 2624 devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) { 2625 log(" USB Headset detected, continue test"); 2626 usb_available = true; 2627 break; 2628 } 2629 } 2630 2631 } else { 2632 log("This system version does not support USB Audio Route check, continue test"); 2633 break; 2634 } 2635 2636 log(" USB-> Check MediaRoute"); 2637 UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); 2638 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { 2639 HashMap<String, UsbDevice> usbDevices = manager.getDeviceList(); 2640 for (Map.Entry<String, UsbDevice> entry : usbDevices.entrySet()) { 2641 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 2642 if (entry.getValue().getProductName().contains("USB Audio")) { 2643 log(" USB Headset detected inside UsbManager, continue test"); 2644 usb_available = true; 2645 log(" USB list: key " + entry.getKey() + " and value: " + String.valueOf(entry.getValue())); 2646 break; 2647 } 2648 } 2649 } 2650 2651 } 2652 if (usb_available) { 2653 long elapsed = System.currentTimeMillis() - startTime; 2654 log("USB detection takes " + String.valueOf(elapsed) + " ms"); 2655 break; 2656 } 2657 try { 2658 Thread.sleep(500); 2659 } catch (InterruptedException e) { 2660 log("Got interrupted during USB Audio Route check"); 2661 e.printStackTrace(); 2662 } 2663 } 2664 } 2665 2666 } 2667