1 /* 2 * Copyright (C) 2015 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.app.Activity; 20 import android.content.Intent; 21 import android.graphics.Color; 22 import android.os.Bundle; 23 import android.util.Log; 24 import android.view.Gravity; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.AdapterView; 28 import android.widget.AdapterView.OnItemSelectedListener; 29 import android.widget.ArrayAdapter; 30 import android.widget.CompoundButton; 31 import android.widget.PopupWindow; 32 import android.widget.Spinner; 33 import android.widget.TextView; 34 import android.widget.ToggleButton; 35 36 37 /** 38 * This activity displays all settings that can be adjusted by the user. 39 */ 40 41 public class SettingsActivity extends Activity implements OnItemSelectedListener, 42 ToggleButton.OnCheckedChangeListener { 43 44 private static final String TAG = "SettingsActivity"; 45 46 private Spinner mSpinnerMicSource; 47 private Spinner mSpinnerPerformanceMode; 48 private Spinner mSpinnerSamplingRate; 49 private Spinner mSpinnerAudioThreadType; 50 private TextView mTextSettingsInfo; 51 private Spinner mSpinnerChannelIndex; 52 private SettingsPicker mPlayerBufferUI; 53 private SettingsPicker mRecorderBufferUI; 54 private SettingsPicker mBufferTestDurationUI; 55 private SettingsPicker mWavePlotDurationUI; 56 private SettingsPicker mLoadThreadUI; 57 private SettingsPicker mNumCapturesUI; 58 private SettingsPicker mIgnoreFirstFramesUI; 59 private ToggleButton mSystraceToggleButton; 60 private ToggleButton mBugreportToggleButton; 61 private ToggleButton mWavCaptureToggleButton; 62 private ToggleButton mSoundLevelCalibrationToggleButton; 63 64 ArrayAdapter<CharSequence> mAdapterSamplingRate; 65 66 67 @Override 68 public void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 // Set the layout for this activity. You can find it 71 View view = getLayoutInflater().inflate(R.layout.settings_activity, null); 72 setContentView(view); 73 mTextSettingsInfo = (TextView) findViewById(R.id.textSettingsInfo); 74 75 int micSource = getApp().getMicSource(); 76 mSpinnerMicSource = (Spinner) findViewById(R.id.spinnerMicSource); 77 ArrayAdapter<CharSequence> adapterMicSource = ArrayAdapter.createFromResource(this, 78 R.array.mic_source_array, android.R.layout.simple_spinner_item); 79 // Specify the layout to use when the list of choices appears 80 adapterMicSource.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 81 // Apply the adapter to the spinner 82 mSpinnerMicSource.setAdapter(adapterMicSource); 83 //set current value 84 mSpinnerMicSource.setSelection(micSource, false); 85 mSpinnerMicSource.setOnItemSelectedListener(this); 86 87 int performanceMode = getApp().getPerformanceMode(); 88 mSpinnerPerformanceMode = (Spinner) findViewById(R.id.spinnerPerformanceMode); 89 ArrayAdapter<CharSequence> adapterPerformanceMode = ArrayAdapter.createFromResource(this, 90 R.array.performance_mode_array, android.R.layout.simple_spinner_item); 91 // Specify the layout to use when the list of choices appears 92 adapterPerformanceMode.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item 93 ); 94 // Apply the adapter to the spinner 95 mSpinnerPerformanceMode.setAdapter(adapterPerformanceMode); 96 //set current value 97 mSpinnerPerformanceMode.setSelection(performanceMode + 1, false); 98 mSpinnerPerformanceMode.setOnItemSelectedListener(this); 99 100 int samplingRate = getApp().getSamplingRate(); 101 //init spinner, etc 102 mSpinnerSamplingRate = (Spinner) findViewById(R.id.spinnerSamplingRate); 103 mAdapterSamplingRate = ArrayAdapter.createFromResource(this, 104 R.array.samplingRate_array, android.R.layout.simple_spinner_item); 105 // Specify the layout to use when the list of choices appears 106 mAdapterSamplingRate.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 107 // Apply the adapter to the spinner 108 mSpinnerSamplingRate.setAdapter(mAdapterSamplingRate); 109 //set current value 110 String currentValue = String.valueOf(samplingRate); 111 int nPosition = mAdapterSamplingRate.getPosition(currentValue); 112 mSpinnerSamplingRate.setSelection(nPosition, false); 113 mSpinnerSamplingRate.setOnItemSelectedListener(this); 114 115 //spinner native 116 int audioThreadType = getApp().getAudioThreadType(); 117 mSpinnerAudioThreadType = (Spinner) findViewById(R.id.spinnerAudioThreadType); 118 ArrayAdapter<CharSequence> adapterThreadType = new ArrayAdapter<CharSequence>(this, 119 android.R.layout.simple_spinner_item, 120 getResources().getTextArray(R.array.audioThreadType_array)) { 121 @Override 122 public boolean isEnabled(int position) { 123 switch (position) { 124 case Constant.AUDIO_THREAD_TYPE_JAVA: return true; 125 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: return getApp().isSafeToUseSles(); 126 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: 127 return getApp().isSafeToUseAAudio(); 128 } 129 return false; 130 } 131 @Override 132 public View getDropDownView(int position, View convertView, ViewGroup parent) { 133 TextView mTextView = (TextView)super.getDropDownView(position, convertView, parent); 134 // TODO: Use theme colors 135 mTextView.setTextColor(isEnabled(position) ? Color.BLACK : Color.GRAY); 136 return mTextView; 137 } 138 }; 139 // Specify the layout to use when the list of choices appears 140 adapterThreadType.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 141 // Apply the adapter to the spinner 142 mSpinnerAudioThreadType.setAdapter(adapterThreadType); 143 //set current value 144 mSpinnerAudioThreadType.setSelection(audioThreadType, false); 145 mSpinnerAudioThreadType.setOnItemSelectedListener(this); 146 147 mSpinnerChannelIndex = (Spinner) findViewById(R.id.spinnerChannelIndex); 148 ArrayAdapter<CharSequence> adapter3 = ArrayAdapter.createFromResource(this, 149 R.array.channelIndex_array, android.R.layout.simple_spinner_item); 150 // Specify the layout to use when the list of choices appears 151 adapter3.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 152 // Apply the adapter to the spinner 153 mSpinnerChannelIndex.setAdapter(adapter3); 154 mSpinnerChannelIndex.setOnItemSelectedListener(this); 155 156 // Settings Picker for Buffer Test Duration 157 mBufferTestDurationUI = (SettingsPicker) findViewById(R.id.bufferTestDurationSetting); 158 mBufferTestDurationUI.setMinMaxDefault(Constant.BUFFER_TEST_DURATION_SECONDS_MIN, 159 Constant.BUFFER_TEST_DURATION_SECONDS_MAX, getApp().getBufferTestDuration()); 160 mBufferTestDurationUI.setTitle(getResources().getString(R.string.labelBufferTestDuration, 161 Constant.BUFFER_TEST_DURATION_SECONDS_MAX)); 162 mBufferTestDurationUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 163 @Override 164 public void settingChanged(int seconds) { 165 log("buffer test new duration: " + seconds); 166 getApp().setBufferTestDuration(seconds); 167 setSettingsHaveChanged(); 168 } 169 }); 170 171 // Settings Picker for Wave Plot Duration 172 mWavePlotDurationUI = (SettingsPicker) findViewById(R.id.wavePlotDurationSetting); 173 mWavePlotDurationUI.setMinMaxDefault(Constant.BUFFER_TEST_WAVE_PLOT_DURATION_SECONDS_MIN, 174 Constant.BUFFER_TEST_WAVE_PLOT_DURATION_SECONDS_MAX, 175 getApp().getBufferTestWavePlotDuration()); 176 mWavePlotDurationUI.setTitle(getResources().getString( 177 R.string.labelBufferTestWavePlotDuration, 178 Constant.BUFFER_TEST_WAVE_PLOT_DURATION_SECONDS_MAX)); 179 mWavePlotDurationUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 180 @Override 181 public void settingChanged(int value) { 182 log("buffer test's wave plot new duration:" + value); 183 getApp().setBufferTestWavePlotDuration(value); 184 setSettingsHaveChanged(); 185 } 186 }); 187 188 // Settings Picker for Player Buffer Period 189 mPlayerBufferUI = (SettingsPicker) findViewById(R.id.playerBufferSetting); 190 mPlayerBufferUI.setMinMaxDefault(Constant.PLAYER_BUFFER_FRAMES_MIN, 191 Constant.PLAYER_BUFFER_FRAMES_MAX, 192 getApp().getPlayerBufferSizeInBytes() / Constant.BYTES_PER_FRAME); 193 mPlayerBufferUI.setTitle(getResources().getString( 194 R.string.labelPlayerBuffer, Constant.PLAYER_BUFFER_FRAMES_MAX)); 195 mPlayerBufferUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 196 @Override 197 public void settingChanged(int value) { 198 log("player buffer new size " + value); 199 getApp().setPlayerBufferSizeInBytes(value * Constant.BYTES_PER_FRAME); 200 int audioThreadType = mSpinnerAudioThreadType.getSelectedItemPosition(); 201 // in native mode, recorder buffer size = player buffer size 202 if (audioThreadType == Constant.AUDIO_THREAD_TYPE_NATIVE_SLES) { 203 getApp().setRecorderBufferSizeInBytes(value * Constant.BYTES_PER_FRAME); 204 mRecorderBufferUI.setValue(value); 205 } 206 setSettingsHaveChanged(); 207 } 208 }); 209 210 // Settings Picker for Recorder Buffer Period 211 mRecorderBufferUI = (SettingsPicker) findViewById(R.id.recorderBufferSetting); 212 mRecorderBufferUI.setMinMaxDefault(Constant.RECORDER_BUFFER_FRAMES_MIN, 213 Constant.RECORDER_BUFFER_FRAMES_MAX, 214 getApp().getRecorderBufferSizeInBytes() / Constant.BYTES_PER_FRAME); 215 mRecorderBufferUI.setTitle(getResources().getString(R.string.labelRecorderBuffer, 216 Constant.RECORDER_BUFFER_FRAMES_MAX)); 217 mRecorderBufferUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 218 @Override 219 public void settingChanged(int value) { 220 log("recorder buffer new size:" + value); 221 getApp().setRecorderBufferSizeInBytes(value * Constant.BYTES_PER_FRAME); 222 setSettingsHaveChanged(); 223 } 224 }); 225 226 // Settings Picker for Number of Load Threads 227 mLoadThreadUI = (SettingsPicker) findViewById(R.id.numLoadThreadsSetting); 228 mLoadThreadUI.setMinMaxDefault(Constant.MIN_NUM_LOAD_THREADS, Constant.MAX_NUM_LOAD_THREADS, 229 getApp().getNumberOfLoadThreads()); 230 mLoadThreadUI.setTitle(getResources().getString(R.string.loadThreadsLabel)); 231 mLoadThreadUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 232 @Override 233 public void settingChanged(int value) { 234 log("new num load threads:" + value); 235 getApp().setNumberOfLoadThreads(value); 236 setSettingsHaveChanged(); 237 } 238 }); 239 240 // Settings Picker for Number of Captures 241 mNumCapturesUI = (SettingsPicker) findViewById(R.id.numCapturesSettingPicker); 242 mNumCapturesUI.setMinMaxDefault(Constant.MIN_NUM_CAPTURES, Constant.MAX_NUM_CAPTURES, 243 getApp().getNumStateCaptures()); 244 mNumCapturesUI.setTitle(getResources().getString(R.string.numCapturesSetting)); 245 mNumCapturesUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 246 @Override 247 public void settingChanged(int value) { 248 log("new num captures:" + value); 249 getApp().setNumberOfCaptures(value); 250 setSettingsHaveChanged(); 251 } 252 }); 253 254 mWavCaptureToggleButton = (ToggleButton) findViewById(R.id.wavSnippetsEnabledToggle); 255 mWavCaptureToggleButton.setChecked(getApp().isCaptureWavSnippetsEnabled()); 256 mWavCaptureToggleButton.setOnCheckedChangeListener(this); 257 258 mBugreportToggleButton = (ToggleButton) findViewById(R.id.BugreportEnabledToggle); 259 mBugreportToggleButton.setChecked(getApp().isCaptureBugreportEnabled()); 260 mBugreportToggleButton.setOnCheckedChangeListener(this); 261 262 mSystraceToggleButton = (ToggleButton) findViewById(R.id.SystraceEnabledToggle); 263 mSystraceToggleButton.setChecked(getApp().isCaptureSysTraceEnabled()); 264 mSystraceToggleButton.setOnCheckedChangeListener(this); 265 266 mSoundLevelCalibrationToggleButton = (ToggleButton) 267 findViewById(R.id.soundLevelCalibrationEnabledToggle); 268 mSoundLevelCalibrationToggleButton.setChecked(getApp().isSoundLevelCalibrationEnabled()); 269 mSoundLevelCalibrationToggleButton.setOnCheckedChangeListener(this); 270 271 // Settings Picker for number of frames to ignore at the beginning 272 mIgnoreFirstFramesUI = (SettingsPicker) findViewById(R.id.ignoreFirstFramesSettingPicker); 273 mIgnoreFirstFramesUI.setMinMaxDefault(Constant.MIN_IGNORE_FIRST_FRAMES, 274 Constant.MAX_IGNORE_FIRST_FRAMES, getApp().getIgnoreFirstFrames()); 275 mIgnoreFirstFramesUI.setTitle(getResources().getString(R.string.labelIgnoreFirstFrames, 276 Constant.MAX_IGNORE_FIRST_FRAMES)); 277 mIgnoreFirstFramesUI.setSettingsChangeListener(new SettingsPicker.SettingChangeListener() { 278 @Override 279 public void settingChanged(int frames) { 280 log("new number of first frames to ignore: " + frames); 281 getApp().setIgnoreFirstFrames(frames); 282 setSettingsHaveChanged(); 283 } 284 }); 285 286 refresh(); 287 } 288 289 290 public void onDestroy() { 291 super.onDestroy(); 292 } 293 294 295 @Override 296 public void onBackPressed() { 297 log("on back pressed"); 298 setSettingsHaveChanged(); 299 finish(); 300 } 301 302 private boolean canPerformBufferTest() { 303 switch (getApp().getAudioThreadType()) { 304 case Constant.AUDIO_THREAD_TYPE_JAVA: 305 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: 306 return true; 307 } 308 // Buffer test isn't yet implemented for AAudio. 309 return false; 310 } 311 312 private void refresh() { 313 mSpinnerMicSource.setEnabled( 314 getApp().getAudioThreadType() == Constant.AUDIO_THREAD_TYPE_JAVA || 315 getApp().getAudioThreadType() == Constant.AUDIO_THREAD_TYPE_NATIVE_SLES); 316 317 boolean bufferTestEnabled = canPerformBufferTest(); 318 mBufferTestDurationUI.setEnabled(bufferTestEnabled); 319 mBufferTestDurationUI.setValue(getApp().getBufferTestDuration()); 320 mWavePlotDurationUI.setEnabled(bufferTestEnabled); 321 mWavePlotDurationUI.setValue(getApp().getBufferTestWavePlotDuration()); 322 323 mPlayerBufferUI.setValue(getApp().getPlayerBufferSizeInBytes() / Constant.BYTES_PER_FRAME); 324 mRecorderBufferUI.setValue( 325 getApp().getRecorderBufferSizeInBytes() / Constant.BYTES_PER_FRAME); 326 327 mRecorderBufferUI.setEnabled( 328 getApp().getAudioThreadType() == Constant.AUDIO_THREAD_TYPE_JAVA || 329 getApp().getAudioThreadType() == Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO); 330 331 int samplingRate = getApp().getSamplingRate(); 332 String currentValue = String.valueOf(samplingRate); 333 int nPosition = mAdapterSamplingRate.getPosition(currentValue); 334 mSpinnerSamplingRate.setSelection(nPosition); 335 336 337 if (getApp().getAudioThreadType() == Constant.AUDIO_THREAD_TYPE_JAVA) { 338 mSpinnerChannelIndex.setSelection(getApp().getChannelIndex() + 1, false); 339 mSpinnerChannelIndex.setEnabled(true); 340 } else { 341 mSpinnerChannelIndex.setSelection(0, false); 342 mSpinnerChannelIndex.setEnabled(false); 343 } 344 345 mNumCapturesUI.setEnabled(getApp().isCaptureEnabled() || 346 getApp().isCaptureWavSnippetsEnabled()); 347 348 String info = getApp().getSystemInfo(); 349 mTextSettingsInfo.setText("SETTINGS - " + info); 350 } 351 352 353 public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { 354 // An item was selected. You can retrieve the selected item using 355 // parent.getItemAtPosition(pos) 356 log("item selected!"); 357 358 switch (parent.getId()) { 359 case R.id.spinnerSamplingRate: 360 String stringValue = mSpinnerSamplingRate.getSelectedItem().toString(); 361 int samplingRate = Integer.parseInt(stringValue); 362 getApp().setSamplingRate(samplingRate); 363 setSettingsHaveChanged(); 364 log("Sampling Rate: " + stringValue); 365 refresh(); 366 break; 367 case R.id.spinnerAudioThreadType: 368 int audioThreadType = mSpinnerAudioThreadType.getSelectedItemPosition(); 369 getApp().setAudioThreadType(audioThreadType); 370 getApp().computeDefaults(); 371 setSettingsHaveChanged(); 372 log("AudioThreadType:" + audioThreadType); 373 refresh(); 374 break; 375 case R.id.spinnerChannelIndex: 376 int channelIndex = mSpinnerChannelIndex.getSelectedItemPosition() - 1; 377 getApp().setChannelIndex(channelIndex); 378 setSettingsHaveChanged(); 379 log("channelIndex:" + channelIndex); 380 refresh(); 381 break; 382 case R.id.spinnerMicSource: 383 int micSource = mSpinnerMicSource.getSelectedItemPosition(); 384 getApp().setMicSource(micSource); 385 setSettingsHaveChanged(); 386 log("mic Source:" + micSource); 387 refresh(); 388 break; 389 case R.id.spinnerPerformanceMode: 390 int performanceMode = mSpinnerPerformanceMode.getSelectedItemPosition() - 1; 391 getApp().setPerformanceMode(performanceMode); 392 getApp().computeDefaults(); 393 setSettingsHaveChanged(); 394 log("performanceMode:" + performanceMode); 395 refresh(); 396 break; 397 } 398 } 399 400 @Override 401 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 402 if (buttonView.getId() == mWavCaptureToggleButton.getId()) { 403 getApp().setCaptureWavsEnabled(isChecked); 404 } else if (buttonView.getId() == mSystraceToggleButton.getId()) { 405 getApp().setCaptureSysTraceEnabled(isChecked); 406 } else if (buttonView.getId() == mBugreportToggleButton.getId()) { 407 getApp().setCaptureBugreportEnabled(isChecked); 408 } else if (buttonView.getId() == mSoundLevelCalibrationToggleButton.getId()) { 409 getApp().setSoundLevelCalibrationEnabled(isChecked); 410 } 411 mNumCapturesUI.setEnabled(getApp().isCaptureEnabled() || 412 getApp().isCaptureWavSnippetsEnabled()); 413 } 414 415 private void setSettingsHaveChanged() { 416 Intent intent = new Intent(); 417 setResult(RESULT_OK, intent); 418 } 419 420 421 public void onNothingSelected(AdapterView<?> parent) { 422 // Another interface callback 423 } 424 425 public void onButtonHelp(View view) { 426 // Create a PopUpWindow with scrollable TextView 427 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null); 428 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT, 429 ViewGroup.LayoutParams.MATCH_PARENT, true); 430 431 TextView helpText = 432 (TextView) popUp.getContentView().findViewById(R.id.ReportInfo); 433 if (view.getId() == R.id.buttonSystraceHelp || view.getId() == R.id.buttonBugreportHelp) { 434 helpText.setText(getResources().getString(R.string.systraceHelp)); 435 } else if (view.getId() == R.id.buttonCalibrateSoundLevelHelp) { 436 helpText.setText(getResources().getString(R.string.calibrateSoundLevelHelp)); 437 } 438 439 // display pop up window, dismissible with back button 440 popUp.showAtLocation((View) findViewById(R.id.settingsMainLayout), Gravity.TOP, 0, 0); 441 } 442 443 /** Called when the user clicks the button */ 444 public void onButtonClick(View view) { 445 getApp().computeDefaults(); 446 refresh(); 447 } 448 449 // Below is work in progress by Ricardo 450 // public void onButtonRecordDefault(View view) { 451 // int samplingRate = getApp().getSamplingRate(); 452 // 453 // int minRecorderBufferSizeInBytes = AudioRecord.getMinBufferSize(samplingRate, 454 // AudioFormat.CHANNEL_IN_MONO, 455 // AudioFormat.ENCODING_PCM_16BIT); 456 // getApp().setRecorderBufferSizeInBytes(minRecorderBufferSizeInBytes); 457 // 458 // refresh(); 459 // } 460 461 // private void computeDefaults() { 462 // 463 //// if (getApp().getAudioThreadType() == LoopbackApplication.AUDIO_THREAD_TYPE_JAVA) 464 //// mNumberPickerRecorderBuffer.setEnabled(true); 465 //// else 466 //// mNumberPickerRecorderBuffer.setEnabled(false); 467 // 468 // int samplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); 469 // getApp().setSamplingRate(samplingRate); 470 // int minPlayerBufferSizeInBytes = AudioTrack.getMinBufferSize(samplingRate, 471 // AudioFormat.CHANNEL_OUT_MONO, 472 // AudioFormat.ENCODING_PCM_16BIT); 473 // getApp().setPlayerBufferSizeInBytes(minPlayerBufferSizeInBytes); 474 // 475 // int minRecorderBufferSizeInBytes = AudioRecord.getMinBufferSize(samplingRate, 476 // AudioFormat.CHANNEL_IN_MONO, 477 // AudioFormat.ENCODING_PCM_16BIT); 478 // getApp().setRecorderBufferSizeInBytes(minRecorderBufferSizeInBytes); 479 // getApp().setRecorderBufferSizeInBytes(minRecorderBufferSizeInBytes); 480 // 481 // log("computed defaults"); 482 // 483 // } 484 485 private LoopbackApplication getApp() { 486 return (LoopbackApplication) this.getApplication(); 487 } 488 489 490 private static void log(String msg) { 491 Log.v(TAG, msg); 492 } 493 494 495 } 496