package com.tutpro.baresip.plus import android.os.Build import android.os.Bundle import android.util.TypedValue import android.view.Gravity import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.* import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.updatePadding import com.tutpro.baresip.plus.databinding.ActivityAudioBinding class AudioActivity : AppCompatActivity() { private lateinit var binding: ActivityAudioBinding private lateinit var micGain: EditText private lateinit var opusBitRate: EditText private lateinit var opusPacketLoss: EditText private lateinit var aec: CheckBox private lateinit var speakerPhone: CheckBox private lateinit var audioDelay: EditText private var save = false private var restart = false private var callVolume = BaresipService.callVolume private var oldMicGain = "" private var oldAudioModules = mutableMapOf() private var oldOpusBitrate = "" private var oldOpusPacketLoss = "" private var oldAec = false private var toneCountry = BaresipService.toneCountry private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { goBack() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityAudioBinding.inflate(layoutInflater) setContentView(binding.root) ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v: View, insets: WindowInsetsCompat -> val systemBars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) if (Build.VERSION.SDK_INT >= 35) binding.AudioView.updatePadding(top = systemBars.top + 56) WindowInsetsCompat.CONSUMED } if (!Utils.isDarkTheme(this)) WindowInsetsControllerCompat(window, binding.root).isAppearanceLightStatusBars = true supportActionBar?.setDisplayHomeAsUpEnabled(true) Utils.addActivity("audio") val callVolSpinner = binding.VolumeSpinner val volKeys = arrayListOf("None", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10") val volVals = arrayListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val curVal = callVolume val curKey = volKeys[curVal] volKeys.removeAt(curVal) volVals.removeAt(curVal) volKeys.add(0, curKey) volVals.add(0, curVal) val callVolAdapter = ArrayAdapter( this, android.R.layout.simple_spinner_item, volKeys ) callVolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) callVolSpinner.adapter = callVolAdapter callVolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( parent: AdapterView<*>, view: View?, position: Int, id: Long ) { callVolume = volVals[volKeys.indexOf(parent.selectedItem.toString())] } override fun onNothingSelected(parent: AdapterView<*>) { } } if (!BaresipService.agcAvailable) { micGain = binding.MicGain oldMicGain = Config.variable("augain") micGain.setText(oldMicGain) } else { binding.MicGainLayout.visibility = View.GONE } speakerPhone = binding.SpeakerPhone speakerPhone.isChecked = BaresipService.speakerPhone val modules = Config.variables("module") val audioModulesList = binding.AudioModulesList var id = 1000 for (module in audioModules) { val rl = RelativeLayout(this) val rlParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) rlParams.marginStart = 16 rl.layoutParams = rlParams val tv = TextView(this) tv.id = id++ val tvParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) tvParams.addRule(RelativeLayout.ALIGN_PARENT_START) tvParams.addRule(RelativeLayout.CENTER_VERTICAL) tvParams.addRule(RelativeLayout.START_OF, id) tv.layoutParams = tvParams tv.text = String.format(getString(R.string.bullet_item), module) tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f) tv.setTextColor(ContextCompat.getColor(this, R.color.colorItemText)) rl.addView(tv) val cb = CheckBox(this) cb.id = id++ val cbParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT) cbParams.addRule(RelativeLayout.ALIGN_PARENT_END) cbParams.addRule(RelativeLayout.CENTER_VERTICAL) cb.layoutParams = cbParams cb.gravity = Gravity.END cb.isChecked = modules.contains("${module}.so") oldAudioModules[module] = cb.isChecked rl.addView(cb) audioModulesList.addView(rl) } opusBitRate = binding.OpusBitRate oldOpusBitrate = Config.variable("opus_bitrate") opusBitRate.setText(oldOpusBitrate) opusPacketLoss = binding.OpusPacketLoss oldOpusPacketLoss = Config.variable("opus_packet_loss") opusPacketLoss.setText(oldOpusPacketLoss) aec = binding.Aec oldAec = modules.contains("webrtc_aecm.so") aec.isChecked = oldAec audioDelay = binding.AudioDelay audioDelay.setText("${BaresipService.audioDelay}") val toneCountrySpinner = binding.ToneCountrySpinner val toneCountryKeys = arrayListOf("bg", "br", "de", "cz", "es", "fi", "fr", "uk", "jp", "no", "nz", "se", "ru", "us") val toneCountryVals = arrayListOf("BG", "BR", "DE", "CZ", "ES", "FI", "FR", "GB", "JP", "NO", "NZ", "SE", "RU", "US") val keyIx = toneCountryKeys.indexOf(toneCountry) val keyVal = toneCountryVals.elementAt(keyIx) toneCountryKeys.removeAt(keyIx) toneCountryVals.removeAt(keyIx) toneCountryKeys.add(0, toneCountry) toneCountryVals.add(0, keyVal) val toneCountryAdapter = ArrayAdapter(this,android.R.layout.simple_spinner_item, toneCountryVals) toneCountryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) toneCountrySpinner.adapter = toneCountryAdapter toneCountrySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { toneCountry = toneCountryKeys[toneCountryVals.indexOf(parent.selectedItem.toString())] } override fun onNothingSelected(parent: AdapterView<*>) { } } bindTitles() onBackPressedDispatcher.addCallback(this, onBackPressedCallback) } override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) val inflater = menuInflater inflater.inflate(R.menu.check_icon, menu) return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (BaresipService.activities.indexOf("audio") == -1) return true when (item.itemId) { R.id.checkIcon -> { if (BaresipService.callVolume != callVolume) { BaresipService.callVolume = callVolume Config.replaceVariable("call_volume", callVolume.toString()) save = true } var gain = "1.0" if (!BaresipService.agcAvailable) { gain = micGain.text.toString().trim() if (!gain.contains(".")) gain = "$gain.0" if (gain != oldMicGain) { if (!checkMicGain(gain)) { Utils.alertView( this, getString(R.string.notice), "${getString(R.string.invalid_microphone_gain)}: $gain." ) micGain.setText(oldMicGain) return false } if (gain == "1.0") { Api.module_unload("augain") Config.removeVariableValue("module", "augain.so") Config.replaceVariable("augain", "1.0") } else { if (oldMicGain == "1.0") { if (Api.module_load("augain") != 0) { Utils.alertView( this, getString(R.string.error), getString(R.string.failed_to_load_module) ) micGain.setText(oldMicGain) return false } Config.addVariable("module", "augain.so") } Config.replaceVariable("augain", gain) Api.cmd_exec("augain $gain") } save = true } } if (speakerPhone.isChecked != BaresipService.speakerPhone) { BaresipService.speakerPhone = speakerPhone.isChecked Config.replaceVariable("speaker_phone", if (BaresipService.speakerPhone) "yes" else "no") save = true } var id = 1001 for (module in audioModules) { val box = findViewById(id++) if (box.isChecked && !oldAudioModules[module]!!) { if (Api.module_load("${module}.so") != 0) { Utils.alertView(this, getString(R.string.error), "${getString(R.string.failed_to_load_module)}: ${module}.so") return false } Config.addVariable("module", "${module}.so") save = true } if (!box.isChecked && oldAudioModules[module]!!) { Api.module_unload("${module}.so") Config.removeVariableValue("module", "${module}.so") for (ua in BaresipService.uas) ua.account.removeAudioCodecs(module) AccountsActivity.saveAccounts() save = true } id++ } val opusBitRate = opusBitRate.text.toString().trim() if (opusBitRate != oldOpusBitrate) { if (!checkOpusBitRate(opusBitRate)) { Utils.alertView(this, getString(R.string.notice), "${getString(R.string.invalid_opus_bitrate)}: $opusBitRate.") return false } Config.replaceVariable("opus_bitrate", opusBitRate) restart = true save = true } val opusPacketLoss = opusPacketLoss.text.toString().trim() if (opusPacketLoss != oldOpusPacketLoss) { if (!checkOpusPacketLoss(opusPacketLoss)) { Utils.alertView(this, getString(R.string.notice), "${getString(R.string.invalid_opus_packet_loss)}: $opusPacketLoss") return false } Config.replaceVariable("opus_packet_loss", opusPacketLoss) restart = true save = true } if (aec.isChecked != oldAec) { if (aec.isChecked) { Config.addVariable("module", "webrtc_aecm.so") if (gain != "1.0") { restart = true } else { if (Api.module_load("webrtc_aecm.so") != 0) { Utils.alertView( this, getString(R.string.error), getString(R.string.failed_to_load_module) ) aec.isChecked = false return false } } BaresipService.webrtcAec = true } else { Api.module_unload("webrtc_aecm.so") Config.removeVariableValue("module", "webrtc_aecm.so") BaresipService.webrtcAec = false } save = true } val audioDelay = audioDelay.text.toString().trim() if (audioDelay != BaresipService.audioDelay.toString()) { if (!checkAudioDelay(audioDelay)) { Utils.alertView(this, getString(R.string.notice), String.format(getString(R.string.invalid_audio_delay), audioDelay)) return false } Config.replaceVariable("audio_delay", audioDelay) BaresipService.audioDelay = audioDelay.toLong() save = true } if (BaresipService.toneCountry != toneCountry) { BaresipService.toneCountry = toneCountry Config.replaceVariable("tone_country", toneCountry) save = true } if (save) Config.save() setResult(if (restart) RESULT_OK else RESULT_CANCELED) BaresipService.activities.remove("audio") finish() return true } android.R.id.home -> { goBack() return true } } return super.onOptionsItemSelected(item) } private fun goBack() { BaresipService.activities.remove("audio") finish() } private fun bindTitles() { binding.VolumeTitle.setOnClickListener { Utils.alertView(this, getString(R.string.default_call_volume), getString(R.string.default_call_volume_help)) } binding.MicGainTitle.setOnClickListener { Utils.alertView(this, getString(R.string.microphone_gain), getString(R.string.microphone_gain_help) ) } binding.SpeakerPhoneTitle.setOnClickListener { Utils.alertView(this, getString(R.string.speaker_phone), getString(R.string.speaker_phone_help)) } binding.AudioModulesTitle.setOnClickListener { Utils.alertView(this, getString(R.string.audio_modules_title), getString(R.string.audio_modules_help)) } binding.OpusBitRateTitle.setOnClickListener { Utils.alertView(this, getString(R.string.opus_bit_rate), getString(R.string.opus_bit_rate_help)) } binding.OpusPacketLossTitle.setOnClickListener { Utils.alertView(this, getString(R.string.opus_packet_loss), getString(R.string.opus_packet_loss_help)) } binding.AecTitle.setOnClickListener { Utils.alertView(this, getString(R.string.aec), getString(R.string.aec_help)) } binding.AudioDelayTitle.setOnClickListener { Utils.alertView(this, getString(R.string.audio_delay), getString(R.string.audio_delay_help)) } binding.ToneCountryTitle.setOnClickListener { Utils.alertView(this, getString(R.string.tone_country), getString(R.string.tone_country_help)) } } private fun checkMicGain(micGain: String): Boolean { val number = try { micGain.toDouble() } catch (e: NumberFormatException) { return false } return number >= 1.0 } private fun checkOpusBitRate(opusBitRate: String): Boolean { val number = opusBitRate.toIntOrNull() ?: return false return (number >= 6000) && (number <= 510000) } private fun checkOpusPacketLoss(opusPacketLoss: String): Boolean { val number = opusPacketLoss.toIntOrNull() ?: return false return (number >= 0) && (number <= 100) } private fun checkAudioDelay(audioDelay: String): Boolean { val number = audioDelay.toIntOrNull() ?: return false return (number >= 100) && (number <= 3000) } companion object { val audioModules = listOf("opus", "amr", "g722", "g7221", "g726", "g729", "codec2", "g711") } }