Added iLBC audio codec

This commit is contained in:
Juha Heinanen
2026-08-16 17:02:32 +03:00
parent d6f6bd37d3
commit 37d99b4848
8 changed files with 396 additions and 314 deletions
+5
View File
@@ -34,6 +34,10 @@ add_library(lib_g729 STATIC IMPORTED)
set_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION set_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a) ${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a)
add_library(lib_ilbc STATIC IMPORTED)
set_target_properties(lib_ilbc PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/ilbc/lib/${ANDROID_ABI}/libilbc.a)
add_library(lib_codec2 STATIC IMPORTED) add_library(lib_codec2 STATIC IMPORTED)
set_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION set_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a) ${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a)
@@ -83,6 +87,7 @@ target_link_libraries(
lib_g722 lib_g722
lib_g722_1 lib_g722_1
lib_g729 lib_g729
lib_ilbc
lib_codec2 lib_codec2
lib_amrnb lib_amrnb
lib_amrwb lib_amrwb
@@ -1,6 +1,5 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.RingtoneManager import android.media.RingtoneManager
import android.net.Uri import android.net.Uri
@@ -44,11 +43,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -58,6 +57,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@@ -71,10 +71,12 @@ enum class Result {
fun NavGraphBuilder.audioScreenRoute(navController: NavController) { fun NavGraphBuilder.audioScreenRoute(navController: NavController) {
composable("audio") { composable("audio") {
val ctx = LocalContext.current val ctx = LocalContext.current
val audioViewModel = viewModel<AudioViewModel>()
AudioScreen( AudioScreen(
viewModel = audioViewModel,
onBack = { navController.navigateUp() }, onBack = { navController.navigateUp() },
checkOnClick = { checkOnClick = {
when (checkOnClick(ctx)) { when (audioViewModel.saveSettings(ctx)) {
Result.OK -> navController.navigateUp() Result.OK -> navController.navigateUp()
Result.RESTART -> { Result.RESTART -> {
navController.previousBackStackEntry navController.previousBackStackEntry
@@ -91,7 +93,16 @@ fun NavGraphBuilder.audioScreenRoute(navController: NavController) {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun AudioScreen(onBack: () -> Unit, checkOnClick: () -> Unit) { private fun AudioScreen(
viewModel: AudioViewModel,
onBack: () -> Unit,
checkOnClick: () -> Unit
) {
val ctx = LocalContext.current
LaunchedEffect(Unit) {
viewModel.loadSettings(ctx)
}
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize().imePadding(), modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
@@ -126,42 +137,16 @@ private fun AudioScreen(onBack: () -> Unit, checkOnClick: () -> Unit) {
} }
} }
) { ) {
contentPadding -> AudioContent(contentPadding) contentPadding -> AudioContent(viewModel, contentPadding)
} }
} }
private var newCallVolume = BaresipService.callVolume
private var oldMicGain = ""
private var newMicGain = ""
private var oldSpeakerPhone = BaresipService.speakerPhone
private var newSpeakerPhone = oldSpeakerPhone
private var oldAudioModules = ArrayList<String>()
private var newAudioModules = mutableMapOf<String, Boolean>()
private var oldOpusBitrate = ""
private var newOpusBitrate = oldOpusBitrate
private var oldOpusPacketLoss = ""
private var newOpusPacketLoss = oldOpusPacketLoss
private var newAudioDelay = BaresipService.audioDelay.toString()
private var newToneCountry = BaresipService.toneCountry
private var newRingtoneUri = ""
private var save = false
private val alertTitle = mutableStateOf("") private val alertTitle = mutableStateOf("")
private val alertMessage = mutableStateOf("") private val alertMessage = mutableStateOf("")
private val showAlert = mutableStateOf(false) private val showAlert = mutableStateOf(false)
@Composable @Composable
private fun AudioContent(contentPadding: PaddingValues) { private fun AudioContent(viewModel: AudioViewModel, contentPadding: PaddingValues) {
oldSpeakerPhone = Config.variable("speaker_phone") == "yes"
newSpeakerPhone = oldSpeakerPhone
oldAudioModules = Config.variables("module")
for (module in Config.audioModules)
newAudioModules[module] = oldAudioModules.contains("${module}.so")
oldOpusBitrate = Config.variable("opus_bitrate")
oldOpusPacketLoss = Config.variable("opus_packet_loss")
if (!BaresipService.agcAvailable) oldMicGain = Config.variable("augain")
if (showAlert.value) if (showAlert.value)
AlertDialog( AlertDialog(
@@ -177,37 +162,30 @@ private fun AudioContent(contentPadding: PaddingValues) {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(contentPadding) .padding(contentPadding)
.padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 4.dp) .padding(top = 16.dp, bottom = 8.dp, start = 16.dp, end = 4.dp)
.verticalScrollbar(scrollState) .verticalScrollbar(scrollState)
.verticalScroll(state = scrollState), .verticalScroll(state = scrollState),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
Ringtone() Ringtone(viewModel)
ToneCountry() ToneCountry(viewModel)
SpeakerPhone() SpeakerPhone(viewModel)
CallVolume() CallVolume(viewModel)
MicGain() MicGain(viewModel)
AudioModules() AudioModules(viewModel)
OpusBitRate() OpusBitRate(viewModel)
OpusPacketLoss() OpusPacketLoss(viewModel)
AudioDelay() IlbcMode(viewModel)
AudioDelay(viewModel)
} }
} }
@Composable @Composable
private fun Ringtone() { private fun Ringtone(viewModel: AudioViewModel) {
val ringToneTitle = stringResource(R.string.ringtone) val ringToneTitle = stringResource(R.string.ringtone)
val selectRingToneMessage = stringResource(R.string.select_ringtone) val selectRingToneMessage = stringResource(R.string.select_ringtone)
val ctx = LocalContext.current val ringtoneUri by viewModel.ringtoneUri.collectAsState()
var ringtoneUri by remember {
mutableStateOf(
if (Preferences(ctx).ringtoneUri == "")
RingtoneManager.getActualDefaultRingtoneUri(ctx, RingtoneManager.TYPE_RINGTONE).toString()
else
Preferences(ctx).ringtoneUri!!
)
}
newRingtoneUri = ringtoneUri
val launcher = rememberLauncherForActivityResult( val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result: ActivityResult -> ) { result: ActivityResult ->
@@ -218,8 +196,7 @@ private fun Ringtone() {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
if (uri != null) { if (uri != null) {
ringtoneUri = uri.toString() viewModel.ringtoneUri.value = uri.toString()
newRingtoneUri = ringtoneUri
} }
} }
} }
@@ -256,7 +233,7 @@ private fun Ringtone() {
} }
@Composable @Composable
private fun ToneCountry() { private fun ToneCountry(viewModel: AudioViewModel) {
Row( Row(
Modifier.fillMaxWidth().padding(end = 10.dp), Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -275,20 +252,19 @@ private fun ToneCountry() {
}, },
fontSize = 18.sp fontSize = 18.sp
) )
val currentToneCountry by viewModel.toneCountry.collectAsState()
val isDropDownExpanded = remember { mutableStateOf(false) } val isDropDownExpanded = remember { mutableStateOf(false) }
val countryNames = arrayListOf("BG", "BR", "DE", "CZ", "ES", "FI", "FR", "GB", "JP", "NO", "NZ", "SE", "RU", "US") val countryNames = arrayListOf("BG", "BR", "DE", "CZ", "ES", "FI", "FR", "GB", "JP", "NO", "NZ", "SE", "RU", "US")
val countryValues = arrayListOf("bg", "br", "de", "cz", "es", "fi", "fr", "uk", "jp", "no", "nz", "se", "ru", "us") val countryValues = arrayListOf("bg", "br", "de", "cz", "es", "fi", "fr", "uk", "jp", "no", "nz", "se", "ru", "us")
val itemPosition = remember { val itemPosition = countryValues.indexOf(currentToneCountry).let { if (it != -1) it else countryValues.indexOf("us") }
val index = countryValues.indexOf(BaresipService.toneCountry)
mutableIntStateOf(if (index != -1) index else countryValues.indexOf("us"))
}
Box { Box {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { isDropDownExpanded.value = true } modifier = Modifier.clickable { isDropDownExpanded.value = true }
) { ) {
Text(text = countryNames[itemPosition.intValue]) Text(text = countryNames[itemPosition])
Icon( Icon(
imageVector = Icons.Filled.ArrowDropDown, imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null, contentDescription = null,
@@ -304,8 +280,7 @@ private fun ToneCountry() {
text = { Text(text = name) }, text = { Text(text = name) },
onClick = { onClick = {
isDropDownExpanded.value = false isDropDownExpanded.value = false
itemPosition.intValue = index viewModel.toneCountry.value = countryValues[index]
newToneCountry = countryValues[index]
} }
) )
if (index < 10) if (index < 10)
@@ -317,7 +292,7 @@ private fun ToneCountry() {
} }
@Composable @Composable
private fun SpeakerPhone() { private fun SpeakerPhone(viewModel: AudioViewModel) {
Row( Row(
Modifier.fillMaxWidth().padding(end=10.dp), Modifier.fillMaxWidth().padding(end=10.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -335,19 +310,18 @@ private fun SpeakerPhone() {
}, },
fontSize = 18.sp fontSize = 18.sp
) )
var speakerPhone by remember { mutableStateOf(oldSpeakerPhone) } val speakerPhone by viewModel.speakerPhone.collectAsState()
Switch( Switch(
checked = speakerPhone, checked = speakerPhone,
onCheckedChange = { onCheckedChange = {
speakerPhone = it viewModel.speakerPhone.value = it
newSpeakerPhone = speakerPhone
} }
) )
} }
} }
@Composable @Composable
private fun CallVolume() { private fun CallVolume(viewModel: AudioViewModel) {
Row( Row(
Modifier.fillMaxWidth().padding(end=10.dp), Modifier.fillMaxWidth().padding(end=10.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -365,20 +339,19 @@ private fun CallVolume() {
}, },
fontSize = 18.sp fontSize = 18.sp
) )
val currentCallVolume by viewModel.callVolume.collectAsState()
val isDropDownExpanded = remember { mutableStateOf(false) } val isDropDownExpanded = remember { mutableStateOf(false) }
val volNames = listOf("--", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10") val volNames = listOf("--", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
val volValues = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val volValues = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val itemPosition = remember { val itemPosition = volValues.indexOf(currentCallVolume).let { if (it != -1) it else 0 }
val index = volValues.indexOf(BaresipService.callVolume)
mutableIntStateOf(if (index != -1) index else 0)
}
Box { Box {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { isDropDownExpanded.value = true } modifier = Modifier.clickable { isDropDownExpanded.value = true }
) { ) {
Text(text = volNames[itemPosition.intValue]) Text(text = volNames[itemPosition])
Icon( Icon(
imageVector = Icons.Filled.ArrowDropDown, imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null, contentDescription = null,
@@ -394,8 +367,7 @@ private fun CallVolume() {
text = { Text(text = vol) }, text = { Text(text = vol) },
onClick = { onClick = {
isDropDownExpanded.value = false isDropDownExpanded.value = false
itemPosition.intValue = index viewModel.callVolume.value = volValues[index]
newCallVolume = volValues[index]
}) })
if (index < 10) if (index < 10)
HorizontalDivider(thickness = 1.dp) HorizontalDivider(thickness = 1.dp)
@@ -406,7 +378,7 @@ private fun CallVolume() {
} }
@Composable @Composable
private fun MicGain() { private fun MicGain(viewModel: AudioViewModel) {
if (!BaresipService.agcAvailable) if (!BaresipService.agcAvailable)
Row( Row(
Modifier.fillMaxWidth().padding(end = 10.dp), Modifier.fillMaxWidth().padding(end = 10.dp),
@@ -415,15 +387,11 @@ private fun MicGain() {
) { ) {
val microphoneGainTitle = stringResource(R.string.microphone_gain) val microphoneGainTitle = stringResource(R.string.microphone_gain)
val microphoneGainHelp = stringResource(R.string.microphone_gain_help) val microphoneGainHelp = stringResource(R.string.microphone_gain_help)
var micGain by remember { mutableStateOf(oldMicGain) } val micGain by viewModel.micGain.collectAsState()
newMicGain = micGain
OutlinedTextField( OutlinedTextField(
value = micGain, value = micGain,
placeholder = { Text(microphoneGainTitle) }, placeholder = { Text(microphoneGainTitle) },
onValueChange = { onValueChange = { viewModel.micGain.value = it },
micGain = it
newMicGain = micGain
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
@@ -439,7 +407,8 @@ private fun MicGain() {
} }
@Composable @Composable
private fun AudioModules() { private fun AudioModules(viewModel: AudioViewModel) {
val audioModules by viewModel.audioModules.collectAsState()
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
@@ -454,27 +423,29 @@ private fun AudioModules() {
alertMessage.value = audioModulesHelp alertMessage.value = audioModulesHelp
showAlert.value = true showAlert.value = true
}) })
for (module in Config.audioModules) for (module in Config.audioModules) {
Row(horizontalArrangement = Arrangement.Start, Row(horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(start = 18.dp, end = 10.dp) modifier = Modifier.padding(start = 18.dp, end = 10.dp)
) { ) {
Text(text = String.format(stringResource(R.string.bullet_item), module), fontSize = 18.sp) Text(text = String.format(stringResource(R.string.bullet_item), module), fontSize = 18.sp)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(oldAudioModules.contains("${module}.so")) } val checked = audioModules[module] ?: false
Switch( Switch(
checked = checked, checked = checked,
onCheckedChange = { onCheckedChange = {
checked = it val newMap = audioModules.toMutableMap()
newAudioModules[module] = checked newMap[module] = it
viewModel.audioModules.value = newMap
} }
) )
} }
} }
} }
}
@Composable @Composable
private fun OpusBitRate() { private fun OpusBitRate(viewModel: AudioViewModel) {
Row( Row(
Modifier.fillMaxWidth().padding(end = 10.dp), Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -482,15 +453,11 @@ private fun OpusBitRate() {
) { ) {
val opusBitRateTitle = stringResource(R.string.opus_bit_rate) val opusBitRateTitle = stringResource(R.string.opus_bit_rate)
val opusBitRateHelp = stringResource(R.string.opus_bit_rate_help) val opusBitRateHelp = stringResource(R.string.opus_bit_rate_help)
var opusBitrate by remember { mutableStateOf(oldOpusBitrate) } val opusBitrate by viewModel.opusBitrate.collectAsState()
newOpusBitrate = opusBitrate
OutlinedTextField( OutlinedTextField(
value = opusBitrate, value = opusBitrate,
placeholder = { Text(opusBitRateTitle) }, placeholder = { Text(opusBitRateTitle) },
onValueChange = { onValueChange = { viewModel.opusBitrate.value = it },
opusBitrate = it
newOpusBitrate = opusBitrate
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
@@ -506,7 +473,7 @@ private fun OpusBitRate() {
} }
@Composable @Composable
private fun OpusPacketLoss() { private fun OpusPacketLoss(viewModel: AudioViewModel) {
Row( Row(
Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp), Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -514,15 +481,11 @@ private fun OpusPacketLoss() {
) { ) {
val opusPacketLossTitle = stringResource(R.string.opus_packet_loss) val opusPacketLossTitle = stringResource(R.string.opus_packet_loss)
val opusPacketLossHelp = stringResource(R.string.opus_packet_loss_help) val opusPacketLossHelp = stringResource(R.string.opus_packet_loss_help)
var opusPacketLoss by remember { mutableStateOf(oldOpusPacketLoss) } val opusPacketLoss by viewModel.opusPacketLoss.collectAsState()
newOpusPacketLoss = opusPacketLoss
OutlinedTextField( OutlinedTextField(
value = opusPacketLoss, value = opusPacketLoss,
placeholder = { Text(opusPacketLossTitle) }, placeholder = { Text(opusPacketLossTitle) },
onValueChange = { onValueChange = { viewModel.opusPacketLoss.value = it },
opusPacketLoss = it
newOpusPacketLoss = opusPacketLoss
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
@@ -538,7 +501,66 @@ private fun OpusPacketLoss() {
} }
@Composable @Composable
private fun AudioDelay() { private fun IlbcMode(viewModel: AudioViewModel) {
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val ilbcModeTitle = stringResource(R.string.ilbc_mode)
val ilbcModeHelp = stringResource(R.string.ilbc_mode_help)
Text(
text = ilbcModeTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = ilbcModeTitle
alertMessage.value = ilbcModeHelp
showAlert.value = true
},
fontSize = 18.sp
)
val currentIlbcMode by viewModel.ilbcMode.collectAsState()
val isDropDownExpanded = remember { mutableStateOf(false) }
val modeNames = listOf("20ms", "30ms")
val modeValues = listOf("20", "30")
val itemPosition = modeValues.indexOf(currentIlbcMode).let { if (it != -1) it else 1 }
Box {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { isDropDownExpanded.value = true }
) {
Text(text = modeNames[itemPosition])
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
modifier = Modifier.size(36.dp)
)
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = { isDropDownExpanded.value = false }
) {
modeNames.forEachIndexed { index, name ->
DropdownMenuItem(
text = { Text(text = name) },
onClick = {
isDropDownExpanded.value = false
viewModel.ilbcMode.value = modeValues[index]
}
)
if (index < modeNames.size - 1)
HorizontalDivider(thickness = 1.dp)
}
}
}
}
}
@Composable
private fun AudioDelay(viewModel: AudioViewModel) {
Row( Row(
Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp, bottom = 12.dp), Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp, bottom = 12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -546,15 +568,11 @@ private fun AudioDelay() {
) { ) {
val audioDelayTitle = stringResource(R.string.audio_delay) val audioDelayTitle = stringResource(R.string.audio_delay)
val audioDelayHelp = stringResource(R.string.audio_delay_help) val audioDelayHelp = stringResource(R.string.audio_delay_help)
var audioDelay by remember { mutableStateOf(BaresipService.audioDelay.toString()) } val currentAudioDelay by viewModel.audioDelay.collectAsState()
newAudioDelay = audioDelay
OutlinedTextField( OutlinedTextField(
value = audioDelay, value = currentAudioDelay,
placeholder = { Text(audioDelayTitle) }, placeholder = { Text(audioDelayTitle) },
onValueChange = { onValueChange = { viewModel.audioDelay.value = it },
audioDelay = it
newAudioDelay = audioDelay
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
@@ -568,155 +586,3 @@ private fun AudioDelay() {
) )
} }
} }
private fun checkOnClick(ctx: Context): Result {
var restart = false
save = false
if (Preferences(ctx).ringtoneUri != newRingtoneUri) {
Preferences(ctx).ringtoneUri = newRingtoneUri
BaresipService.rt = RingtoneManager.getRingtone(ctx, newRingtoneUri.toUri())
}
if (BaresipService.toneCountry != newToneCountry) {
BaresipService.toneCountry = newToneCountry
Config.replaceVariable("tone_country", newToneCountry)
save = true
}
if (BaresipService.callVolume != newCallVolume) {
BaresipService.callVolume = newCallVolume
Config.replaceVariable("call_volume", newCallVolume.toString())
save = true
}
if (!BaresipService.agcAvailable) {
var gain = newMicGain.trim()
if (!gain.contains(".")) gain = "$gain.0"
if (gain != oldMicGain) {
if (!checkMicGain(gain)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = "${ctx.getString(R.string.invalid_microphone_gain)}: $gain."
showAlert.value = true
return Result.ERROR
}
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) {
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = ctx.getString(R.string.failed_to_load_module) + ": augain.so"
showAlert.value = true
return Result.ERROR
}
Config.addVariable("module", "augain.so")
}
Config.replaceVariable("augain", gain)
Api.cmd_exec("augain $gain")
}
save = true
}
}
if (newSpeakerPhone != oldSpeakerPhone) {
Config.replaceVariable("speaker_phone", if (newSpeakerPhone) "yes" else "no")
BaresipService.speakerPhoneAuto = newSpeakerPhone
save = true
}
for (module in Config.audioModules) {
if (newAudioModules[module] != null) {
if (newAudioModules[module]!!) {
if (!oldAudioModules.contains("${module}.so")) {
if (Api.module_load("${module}.so") != 0) {
alertTitle.value = ctx.getString(R.string.error)
alertMessage.value = "${ctx.getString(R.string.failed_to_load_module)}: ${module}.so"
showAlert.value = true
return Result.ERROR
}
Config.addVariable("module", "${module}.so")
save = true
}
}
else if (oldAudioModules.contains("${module}.so")) {
Api.module_unload("${module}.so")
Config.removeVariableValue("module", "${module}.so")
for (ua in BaresipService.uas.value)
ua.account.removeAudioCodecs(module)
Account.saveAccounts()
save = true
}
}
}
if (newOpusBitrate != oldOpusBitrate) {
if (!checkOpusBitRate(newOpusBitrate)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = "${ctx.getString(R.string.invalid_opus_bitrate)}: $newOpusBitrate."
showAlert.value = true
return Result.ERROR
}
Config.replaceVariable("opus_bitrate", newOpusBitrate)
restart = true
save = true
}
if (newOpusPacketLoss != oldOpusPacketLoss) {
if (!checkOpusPacketLoss(newOpusPacketLoss)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = "${ctx.getString(R.string.invalid_opus_packet_loss)}: $newOpusPacketLoss"
showAlert.value = true
return Result.ERROR
}
Config.replaceVariable("opus_packet_loss", newOpusPacketLoss)
restart = true
save = true
}
val audioDelay = newAudioDelay.trim()
if (audioDelay != BaresipService.audioDelay.toString()) {
if (!checkAudioDelay(audioDelay)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = String.format(ctx.getString(R.string.invalid_audio_delay), audioDelay)
showAlert.value = true
return Result.ERROR
}
Config.replaceVariable("audio_delay", audioDelay)
BaresipService.audioDelay = audioDelay.toLong()
save = true
}
if (save) Config.save()
return if (restart) Result.RESTART else Result.OK
}
private fun checkMicGain(micGain: String): Boolean {
val number =
try {
micGain.toDouble()
} catch (_: 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)
}
@@ -0,0 +1,194 @@
package com.tutpro.baresip
import android.content.Context
import android.media.RingtoneManager
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
class AudioViewModel : ViewModel() {
val speakerPhone = MutableStateFlow(false)
val callVolume = MutableStateFlow(0)
val micGain = MutableStateFlow("")
val audioModules = MutableStateFlow(mutableMapOf<String, Boolean>())
val opusBitrate = MutableStateFlow("")
val opusPacketLoss = MutableStateFlow("")
val ilbcMode = MutableStateFlow("")
val audioDelay = MutableStateFlow("")
val toneCountry = MutableStateFlow("")
val ringtoneUri = MutableStateFlow("")
private var isLoaded = false
var oldSpeakerPhone = false
var oldCallVolume = 0
var oldMicGain = ""
var oldAudioModules = ArrayList<String>()
var oldOpusBitrate = ""
var oldOpusPacketLoss = ""
var oldIlbcMode = ""
var oldAudioDelay = ""
var oldToneCountry = ""
var oldRingtoneUri = ""
fun loadSettings(ctx: Context) {
if (isLoaded || !Config.isInitialized()) return else isLoaded = true
oldSpeakerPhone = Config.variable("speaker_phone") == "yes"
speakerPhone.value = oldSpeakerPhone
oldCallVolume = BaresipService.callVolume
callVolume.value = oldCallVolume
if (!BaresipService.agcAvailable) {
oldMicGain = Config.variable("augain")
micGain.value = oldMicGain
}
oldAudioModules = Config.variables("module")
val modulesMap = mutableMapOf<String, Boolean>()
for (module in Config.audioModules)
modulesMap[module] = oldAudioModules.contains("${module}.so")
audioModules.value = modulesMap
oldOpusBitrate = Config.variable("opus_bitrate")
opusBitrate.value = oldOpusBitrate
oldOpusPacketLoss = Config.variable("opus_packet_loss")
opusPacketLoss.value = oldOpusPacketLoss
oldIlbcMode = Config.variable("ilbc_mode")
ilbcMode.value = oldIlbcMode
oldAudioDelay = Config.variable("audio_delay")
if (oldAudioDelay == "") oldAudioDelay = BaresipService.audioDelay.toString()
audioDelay.value = oldAudioDelay
oldToneCountry = BaresipService.toneCountry
toneCountry.value = oldToneCountry
oldRingtoneUri = Preferences(ctx).ringtoneUri ?: ""
ringtoneUri.value = oldRingtoneUri
}
fun saveSettings(ctx: Context): Result {
var restart = false
var save = false
if (Preferences(ctx).ringtoneUri != ringtoneUri.value) {
Preferences(ctx).ringtoneUri = ringtoneUri.value
BaresipService.rt = RingtoneManager.getRingtone(ctx, ringtoneUri.value.toUri())
}
if (BaresipService.toneCountry != toneCountry.value) {
BaresipService.toneCountry = toneCountry.value
Config.replaceVariable("tone_country", toneCountry.value)
save = true
}
if (BaresipService.callVolume != callVolume.value) {
BaresipService.callVolume = callVolume.value
Config.replaceVariable("call_volume", callVolume.value.toString())
save = true
}
if (!BaresipService.agcAvailable) {
var gain = micGain.value.trim()
if (gain.isNotEmpty() && !gain.contains(".")) gain = "$gain.0"
if (gain != oldMicGain) {
if (!checkMicGain(gain)) return Result.ERROR
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) return Result.ERROR
Config.addVariable("module", "augain.so")
}
Config.replaceVariable("augain", gain)
Api.cmd_exec("augain $gain")
}
save = true
}
}
if (speakerPhone.value != oldSpeakerPhone) {
Config.replaceVariable("speaker_phone", if (speakerPhone.value) "yes" else "no")
BaresipService.speakerPhoneAuto = speakerPhone.value
save = true
}
for (module in Config.audioModules) {
val enabled = audioModules.value[module] ?: false
if (enabled != oldAudioModules.contains("${module}.so")) {
if (enabled) {
if (Api.module_load("${module}.so") != 0) return Result.ERROR
Config.addVariable("module", "${module}.so")
}
else {
Api.module_unload("${module}.so")
Config.removeVariableValue("module", "${module}.so")
for (ua in BaresipService.uas.value)
ua.account.removeAudioCodecs(module)
Account.saveAccounts()
}
save = true
}
}
if (opusBitrate.value != oldOpusBitrate) {
if (!checkOpusBitRate(opusBitrate.value)) return Result.ERROR
Config.replaceVariable("opus_bitrate", opusBitrate.value)
restart = true
save = true
}
if (opusPacketLoss.value != oldOpusPacketLoss) {
if (!checkOpusPacketLoss(opusPacketLoss.value)) return Result.ERROR
Config.replaceVariable("opus_packet_loss", opusPacketLoss.value)
restart = true
save = true
}
if (ilbcMode.value != oldIlbcMode) {
Config.replaceVariable("ilbc_mode", ilbcMode.value)
restart = true
save = true
}
val delay = audioDelay.value.trim()
if (delay != oldAudioDelay) {
if (!checkAudioDelay(delay)) return Result.ERROR
Config.replaceVariable("audio_delay", delay)
BaresipService.audioDelay = delay.toLong()
save = true
}
if (save) Config.save()
return if (restart) Result.RESTART else Result.OK
}
private fun checkMicGain(micGain: String): Boolean {
val number = micGain.toDoubleOrNull() ?: 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)
}
}
@@ -10,7 +10,7 @@ import java.nio.charset.StandardCharsets
object Config { object Config {
private val configPath = BaresipService.filesPath + "/config" private val configPath = BaresipService.filesPath + "/config"
val audioModules = listOf("opus", "amr", "libg722", "g7221", "g729", "codec2", "g711") val audioModules = listOf("opus", "amr", "ilbc", "libg722", "g7221", "g729", "codec2", "g711")
private lateinit var config: String private lateinit var config: String
private lateinit var previousConfig: String private lateinit var previousConfig: String
private lateinit var previousLines: List<String> private lateinit var previousLines: List<String>
@@ -213,7 +213,8 @@ object Config {
val previousModules = previousVariables("module") val previousModules = previousVariables("module")
for (module in audioModules) for (module in audioModules)
if ("${module}.so" in previousModules || if ("${module}.so" in previousModules ||
(module == "libg722" && "g722.so" in previousModules)) (module == "libg722" && "g722.so" in previousModules) ||
module == "ilbc")
config = "${config}module ${module}.so\n" config = "${config}module ${module}.so\n"
Utils.aecAgcNsCheck() Utils.aecAgcNsCheck()
@@ -236,6 +237,12 @@ object Config {
else else
"${config}opus_packet_loss $opusPacketLoss\n" "${config}opus_packet_loss $opusPacketLoss\n"
val ilbcMode = previousVariable("ilbc_mode")
config = if (ilbcMode == "")
"${config}ilbc_mode 30\n"
else
"${config}ilbc_mode $ilbcMode\n"
val audioDelay = previousVariable("audio_delay") val audioDelay = previousVariable("audio_delay")
if (audioDelay != "") { if (audioDelay != "") {
config = "${config}audio_delay $audioDelay\n" config = "${config}audio_delay $audioDelay\n"
@@ -89,9 +89,7 @@ import java.io.FileInputStream
import java.util.Locale import java.util.Locale
import androidx.core.net.toUri import androidx.core.net.toUri
private var restart = false
private val showRestartDialog = mutableStateOf(false) private val showRestartDialog = mutableStateOf(false)
private var save = false
fun NavGraphBuilder.settingsScreenRoute( fun NavGraphBuilder.settingsScreenRoute(
navController: NavController, navController: NavController,
@@ -105,14 +103,14 @@ fun NavGraphBuilder.settingsScreenRoute(
navController = navController, navController = navController,
settingsViewModel = viewModel, settingsViewModel = viewModel,
onBack = { onBack = {
if (restart) if (viewModel.restart)
showRestartDialog.value = true showRestartDialog.value = true
else else
navController.navigateUp() navController.navigateUp()
}, },
checkOnClick = { checkOnClick = {
if (checkOnClick(ctx, viewModel)) { if (checkOnClick(ctx, viewModel)) {
if (restart) if (viewModel.restart)
showRestartDialog.value = true showRestartDialog.value = true
else else
navController.navigateUp() navController.navigateUp()
@@ -142,19 +140,23 @@ private fun SettingsScreen(
?.observeAsState() ?.observeAsState()
LaunchedEffect(audioResult?.value) { LaunchedEffect(audioResult?.value) {
Log.d(TAG, "audio_settings_result observed: ${audioResult?.value}")
if (audioResult?.value == true) { if (audioResult?.value == true) {
Log.d(TAG, "Got result from AudioSettings: true") Log.d(TAG, "Got result from AudioSettings: true")
restart = true settingsViewModel.restart = true
showRestartDialog.value = true
navController.currentBackStackEntry navController.currentBackStackEntry
?.savedStateHandle ?.savedStateHandle
?.remove<Boolean>("audio_settings_result") ?.remove<Boolean>("audio_settings_result")
} }
} }
LaunchedEffect(null) { LaunchedEffect(Unit) {
save = false if (!settingsViewModel.restart) {
restart = false settingsViewModel.save = false
settingsViewModel.restart = false
settingsViewModel.loadSettings(ctx) settingsViewModel.loadSettings(ctx)
}
areSettingsLoaded = true areSettingsLoaded = true
} }
@@ -502,8 +504,8 @@ private fun SettingsContent(
inputStream.close() inputStream.close()
Config.replaceVariable("sip_certificate", certPath) Config.replaceVariable("sip_certificate", certPath)
viewModel.tlsCertificateFile.value = true viewModel.tlsCertificateFile.value = true
save = true viewModel.save = true
restart = true viewModel.restart = true
} catch (e: Error) { } catch (e: Error) {
alertTitle.value = errorTitleText alertTitle.value = errorTitleText
alertMessage.value = readCertError + ": " + e.message alertMessage.value = readCertError + ": " + e.message
@@ -561,8 +563,8 @@ private fun SettingsContent(
Utils.putFileContents(certPath, content) Utils.putFileContents(certPath, content)
Config.replaceVariable("sip_certificate", certPath) Config.replaceVariable("sip_certificate", certPath)
viewModel.tlsCertificateFile.value = true viewModel.tlsCertificateFile.value = true
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
shouldShowRequestPermissionRationale(activity, permission) -> shouldShowRequestPermissionRationale(activity, permission) ->
showAlertDialog.value = true showAlertDialog.value = true
@@ -575,8 +577,8 @@ private fun SettingsContent(
else { else {
Config.removeVariable("sip_certificate") Config.removeVariable("sip_certificate")
Utils.deleteFile(File(BaresipService.filesPath + "/cert.pem")) Utils.deleteFile(File(BaresipService.filesPath + "/cert.pem"))
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
} }
) )
@@ -645,7 +647,7 @@ private fun SettingsContent(
val inputStream = ctx.contentResolver.openInputStream(uri) as FileInputStream val inputStream = ctx.contentResolver.openInputStream(uri) as FileInputStream
caCertsFile.copyInputStreamToFile(inputStream) caCertsFile.copyInputStreamToFile(inputStream)
inputStream.close() inputStream.close()
restart = true viewModel.restart = true
} catch (e: Error) { } catch (e: Error) {
alertTitle.value = errorTitleText alertTitle.value = errorTitleText
alertMessage.value = readCaCertsError + ": " + e.message alertMessage.value = readCaCertsError + ": " + e.message
@@ -696,7 +698,7 @@ private fun SettingsContent(
} }
File(BaresipService.filesPath + "/ca_certs.crt").writeBytes(content) File(BaresipService.filesPath + "/ca_certs.crt").writeBytes(content)
viewModel.caFile.value = true viewModel.caFile.value = true
restart = true viewModel.restart = true
} }
shouldShowRequestPermissionRationale(activity, permission) -> { shouldShowRequestPermissionRationale(activity, permission) -> {
dialogTitle.value = noticeTitleText dialogTitle.value = noticeTitleText
@@ -716,7 +718,7 @@ private fun SettingsContent(
Utils.selectInputFile(caCertsRequest) Utils.selectInputFile(caCertsRequest)
else { else {
Utils.deleteFile(File(BaresipService.filesPath + "/ca_certs.crt")) Utils.deleteFile(File(BaresipService.filesPath + "/ca_certs.crt"))
restart = true viewModel.restart = true
} }
} }
) )
@@ -991,7 +993,7 @@ private fun SettingsContent(
if (results[Manifest.permission.READ_PHONE_NUMBERS] == true) if (results[Manifest.permission.READ_PHONE_NUMBERS] == true)
Log.d(TAG, "READ_PHONE_NUMBERS permission granted") Log.d(TAG, "READ_PHONE_NUMBERS permission granted")
BaresipService.instance?.addMobileUserAgent() BaresipService.instance?.addMobileUserAgent()
restart = true viewModel.restart = true
} }
val dialerRoleRequest = rememberLauncherForActivityResult( val dialerRoleRequest = rememberLauncherForActivityResult(
@@ -1006,14 +1008,14 @@ private fun SettingsContent(
) )
if (Utils.checkPermissions(ctx, permissions)) { if (Utils.checkPermissions(ctx, permissions)) {
BaresipService.instance?.addMobileUserAgent() BaresipService.instance?.addMobileUserAgent()
restart = true viewModel.restart = true
} }
else else
requestPermissionLauncher.launch(permissions) requestPermissionLauncher.launch(permissions)
} }
else else
BaresipService.instance?.addMobileUserAgent() BaresipService.instance?.addMobileUserAgent()
restart = true viewModel.restart = true
} }
Switch( Switch(
checked = defaultDialer, checked = defaultDialer,
@@ -1037,7 +1039,7 @@ private fun SettingsContent(
Log.e(TAG, "ActivityNotFound exception: ${e.message}") Log.e(TAG, "ActivityNotFound exception: ${e.message}")
} }
BaresipService.instance?.addMobileUserAgent() BaresipService.instance?.addMobileUserAgent()
restart = true viewModel.restart = true
} }
} }
) )
@@ -1067,7 +1069,7 @@ private fun SettingsContent(
Config.replaceVariable("mobile_account", if (it) "yes" else "no") Config.replaceVariable("mobile_account", if (it) "yes" else "no")
Config.save() Config.save()
BaresipService.instance?.addMobileUserAgent() BaresipService.instance?.addMobileUserAgent()
restart = true viewModel.restart = true
} }
) )
} }
@@ -1105,7 +1107,7 @@ private fun SettingsContent(
) { _ -> ) { _ ->
val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_SMS) val isHeld = roleManager.isRoleHeld(RoleManager.ROLE_SMS)
viewModel.defaultMessaging.value = isHeld viewModel.defaultMessaging.value = isHeld
restart = true viewModel.restart = true
} }
Switch( Switch(
checked = defaultMessaging, checked = defaultMessaging,
@@ -1280,7 +1282,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
if ((Config.variable("auto_start") == "yes") != viewModel.autoStart.value) { if ((Config.variable("auto_start") == "yes") != viewModel.autoStart.value) {
Config.replaceVariable("auto_start", if (viewModel.autoStart.value) "yes" else "no") Config.replaceVariable("auto_start", if (viewModel.autoStart.value) "yes" else "no")
save = true viewModel.save = true
} }
val listenAddr = viewModel.listenAddress.value.trim() val listenAddr = viewModel.listenAddress.value.trim()
@@ -1292,14 +1294,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
return false return false
} }
Config.replaceVariable("sip_listen", listenAddr) Config.replaceVariable("sip_listen", listenAddr)
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
if (Config.variable("net_af").lowercase() != viewModel.addressFamily.value) { if (Config.variable("net_af").lowercase() != viewModel.addressFamily.value) {
Config.replaceVariable("net_af", viewModel.addressFamily.value) Config.replaceVariable("net_af", viewModel.addressFamily.value)
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
val transportProtocols = viewModel.transportProtocols.value val transportProtocols = viewModel.transportProtocols.value
@@ -1315,8 +1317,8 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
Config.removeVariable("sip_transports") Config.removeVariable("sip_transports")
if (transportProtocols.isNotEmpty()) if (transportProtocols.isNotEmpty())
Config.replaceVariable("sip_transports", transportProtocols) Config.replaceVariable("sip_transports", transportProtocols)
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
val dnsServers = addMissingPorts(viewModel.dnsServers.value val dnsServers = addMissingPorts(viewModel.dnsServers.value
@@ -1345,13 +1347,13 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
Config.updateDnsServers(BaresipService.dnsServers) Config.updateDnsServers(BaresipService.dnsServers)
} }
// Api.net_dns_debug() // Api.net_dns_debug()
save = true viewModel.save = true
} }
if ((Config.variable("sip_verify_server") == "yes") != viewModel.verifyServer.value) { if ((Config.variable("sip_verify_server") == "yes") != viewModel.verifyServer.value) {
Config.replaceVariable("sip_verify_server", if (viewModel.verifyServer.value) "yes" else "no") Config.replaceVariable("sip_verify_server", if (viewModel.verifyServer.value) "yes" else "no")
Api.config_verify_server_set(viewModel.verifyServer.value) Api.config_verify_server_set(viewModel.verifyServer.value)
save = true viewModel.save = true
} }
val userAgent = viewModel.userAgent.value.trim() val userAgent = viewModel.userAgent.value.trim()
@@ -1367,14 +1369,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
Config.replaceVariable("user_agent", userAgent) Config.replaceVariable("user_agent", userAgent)
else else
Config.removeVariable("user_agent") Config.removeVariable("user_agent")
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
if ((Config.variable("sip_cuser_random") == "yes") != viewModel.uniqueContactUri.value) { if ((Config.variable("sip_cuser_random") == "yes") != viewModel.uniqueContactUri.value) {
Config.replaceVariable("sip_cuser_random", if (viewModel.uniqueContactUri.value) "yes" else "no") Config.replaceVariable("sip_cuser_random", if (viewModel.uniqueContactUri.value) "yes" else "no")
save = true viewModel.save = true
restart = true viewModel.restart = true
} }
val darkTheme = viewModel.darkTheme.value val darkTheme = viewModel.darkTheme.value
@@ -1387,14 +1389,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
BaresipService.darkTheme.value = darkTheme BaresipService.darkTheme.value = darkTheme
AppCompatDelegate.setDefaultNightMode(newDisplayTheme) AppCompatDelegate.setDefaultNightMode(newDisplayTheme)
Config.replaceVariable("dark_theme", if (darkTheme) "yes" else "no") Config.replaceVariable("dark_theme", if (darkTheme) "yes" else "no")
save = true viewModel.save = true
} }
val dynamicColors = viewModel.dynamicColors.value val dynamicColors = viewModel.dynamicColors.value
if (BaresipService.dynamicColors.value != dynamicColors) { if (BaresipService.dynamicColors.value != dynamicColors) {
BaresipService.dynamicColors.value = dynamicColors BaresipService.dynamicColors.value = dynamicColors
Config.replaceVariable("dynamic_colors", if (dynamicColors) "yes" else "no") Config.replaceVariable("dynamic_colors", if (dynamicColors) "yes" else "no")
save = true viewModel.save = true
} }
val colorblind = viewModel.colorblind.value val colorblind = viewModel.colorblind.value
@@ -1405,14 +1407,14 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
val baresipService = Intent(ctx, BaresipService::class.java) val baresipService = Intent(ctx, BaresipService::class.java)
baresipService.action = "Update Notification" baresipService.action = "Update Notification"
ContextCompat.startForegroundService(ctx, baresipService) ContextCompat.startForegroundService(ctx, baresipService)
save = true viewModel.save = true
} }
val proximitySensing = viewModel.proximitySensing.value val proximitySensing = viewModel.proximitySensing.value
if ((Config.variable("proximity_sensing") == "yes") != proximitySensing) { if ((Config.variable("proximity_sensing") == "yes") != proximitySensing) {
Config.replaceVariable("proximity_sensing", if (proximitySensing) "yes" else "no") Config.replaceVariable("proximity_sensing", if (proximitySensing) "yes" else "no")
BaresipService.proximitySensing = proximitySensing BaresipService.proximitySensing = proximitySensing
save = true viewModel.save = true
} }
val debug = viewModel.debug.value val debug = viewModel.debug.value
@@ -1421,7 +1423,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
Config.replaceVariable("log_level", logLevelString) Config.replaceVariable("log_level", logLevelString)
Api.log_level_set(logLevelString.toInt()) Api.log_level_set(logLevelString.toInt())
Log.logLevelSet(logLevelString.toInt()) Log.logLevelSet(logLevelString.toInt())
save = true viewModel.save = true
} }
val sipTrace = if (debug) viewModel.sipTrace.value else false val sipTrace = if (debug) viewModel.sipTrace.value else false
@@ -1430,7 +1432,7 @@ private fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {
Api.uag_enable_sip_trace(sipTrace) Api.uag_enable_sip_trace(sipTrace)
} }
if (save) Config.save() if (viewModel.save) Config.save()
return true return true
} }
@@ -7,7 +7,9 @@ import android.content.Context.ROLE_SERVICE
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import java.io.File import java.io.File
@@ -35,6 +37,9 @@ class SettingsViewModel: ViewModel() {
val debug = MutableStateFlow(false) val debug = MutableStateFlow(false)
val sipTrace = MutableStateFlow(false) val sipTrace = MutableStateFlow(false)
var restart by mutableStateOf(false)
var save by mutableStateOf(false)
private var isLoaded = false private var isLoaded = false
fun loadSettings(ctx: Context) { fun loadSettings(ctx: Context) {
@@ -45,10 +50,7 @@ class SettingsViewModel: ViewModel() {
listenAddress.value = Config.variable("sip_listen") listenAddress.value = Config.variable("sip_listen")
val familyValues = listOf("", "ipv4", "ipv6") addressFamily.value = Config.variable("net_af").lowercase()
val itemPosition =
mutableIntStateOf(familyValues.indexOf(Config.variable("net_af").lowercase()))
addressFamily.value = familyValues[itemPosition.intValue]
transportProtocols.value = Config.variable("sip_transports") transportProtocols.value = Config.variable("sip_transports")
+3
View File
@@ -450,6 +450,9 @@
<string name="audio_delay_help">Audion odotusviive (millisekunneissa) soitetun puhelun alkaessa. <string name="audio_delay_help">Audion odotusviive (millisekunneissa) soitetun puhelun alkaessa.
Aseta korkeampi arvo, jos et kuule vastaajan ääntä heti, kun puhelu alkaa.</string> Aseta korkeampi arvo, jos et kuule vastaajan ääntä heti, kun puhelu alkaa.</string>
<string name="invalid_audio_delay">Virheellinen audioviive \'%1$s\'. Sallittu arvo on välillä 1003000.</string> <string name="invalid_audio_delay">Virheellinen audioviive \'%1$s\'. Sallittu arvo on välillä 1003000.</string>
<string name="ilbc_mode">iLBC-moodi</string>
<string name="ilbc_mode_help">Valitsee iLBC-koodekin kehyksen keston.
30ms tarjoaa paremman äänenlaadun alhaisilla bittinopeuksilla, kun taas 20ms:ssä on pienempi viive.</string>
<string name="default_call_volume">Oletus äänen voimakkuus</string> <string name="default_call_volume">Oletus äänen voimakkuus</string>
<string name="default_call_volume_help">Jos valittu, puhelun äänen voimakkuus <string name="default_call_volume_help">Jos valittu, puhelun äänen voimakkuus
asteikolla 110.</string> asteikolla 110.</string>
+3
View File
@@ -429,6 +429,9 @@
<string name="audio_delay_help">Time (in milliseconds) to wait audio from callee when call is established. <string name="audio_delay_help">Time (in milliseconds) to wait audio from callee when call is established.
Set to a higher value if you miss audio from callee at the beginning of the call.</string> Set to a higher value if you miss audio from callee at the beginning of the call.</string>
<string name="invalid_audio_delay">Invalid Audio Delay \'%1$s\'. Valid values are from 100 to 3000.</string> <string name="invalid_audio_delay">Invalid Audio Delay \'%1$s\'. Valid values are from 100 to 3000.</string>
<string name="ilbc_mode">iLBC Mode</string>
<string name="ilbc_mode_help">Selects the default iLBC frame duration.
30ms provides better low-bitrate quality, while 20ms has lower latency.</string>
<string name="default_call_volume">Default Call Volume</string> <string name="default_call_volume">Default Call Volume</string>
<string name="default_call_volume_help">If set, default call audio volume at scale 110.</string> <string name="default_call_volume_help">If set, default call audio volume at scale 110.</string>
<string name="tone_country">Tone Country</string> <string name="tone_country">Tone Country</string>