Added Telecom Framework audio setting

This commit is contained in:
Juha Heinanen
2026-04-28 11:26:13 +03:00
parent 4654adea6c
commit dcd33e8097
6 changed files with 227 additions and 55 deletions

View File

@ -164,6 +164,8 @@ private var newOpusPacketLoss = oldOpusPacketLoss
private var newAudioDelay = BaresipService.audioDelay.toString() private var newAudioDelay = BaresipService.audioDelay.toString()
private var newToneCountry = BaresipService.toneCountry private var newToneCountry = BaresipService.toneCountry
private var newRingtoneUri = "" private var newRingtoneUri = ""
private var oldTelecom = BaresipService.telecom
private var newTelecom = oldTelecom
private var save = false private var save = false
@ -176,6 +178,8 @@ private fun AudioContent(contentPadding: PaddingValues) {
oldSpeakerPhone = Config.variable("speaker_phone") == "yes" oldSpeakerPhone = Config.variable("speaker_phone") == "yes"
newSpeakerPhone = oldSpeakerPhone newSpeakerPhone = oldSpeakerPhone
oldTelecom = Config.variable("telecom") == "yes"
newTelecom = oldTelecom
oldAudioModules = Config.variables("module") oldAudioModules = Config.variables("module")
oldOpusBitrate = Config.variable("opus_bitrate") oldOpusBitrate = Config.variable("opus_bitrate")
oldOpusPacketLoss = Config.variable("opus_packet_loss") oldOpusPacketLoss = Config.variable("opus_packet_loss")
@ -202,6 +206,7 @@ private fun AudioContent(contentPadding: PaddingValues) {
.verticalScroll(state = scrollState), .verticalScroll(state = scrollState),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
Telecom()
ToneCountry() ToneCountry()
Ringtone() Ringtone()
SpeakerPhone() SpeakerPhone()
@ -214,6 +219,37 @@ private fun AudioContent(contentPadding: PaddingValues) {
} }
} }
@Composable
private fun Telecom() {
Row(
Modifier
.fillMaxWidth()
.padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val telecomTitle = stringResource(R.string.telecom)
val telecomHelp = stringResource(R.string.telecom_help)
Text(text = telecomTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = telecomTitle
alertMessage.value = telecomHelp
showAlert.value = true
},
fontSize = 18.sp)
var telecom by remember { mutableStateOf(oldTelecom) }
Switch(
checked = telecom,
onCheckedChange = {
telecom = it
newTelecom = telecom
}
)
}
}
@Composable @Composable
private fun ToneCountry() { private fun ToneCountry() {
Row( Row(
@ -709,6 +745,12 @@ private fun checkOnClick(ctx: Context): Result {
save = true save = true
} }
if (newTelecom != oldTelecom) {
Config.replaceVariable("telecom", if (newTelecom) "yes" else "no")
BaresipService.telecom = newTelecom
save = true
}
if (save) Config.save() if (save) Config.save()
return if (restart) Result.RESTART else Result.OK return if (restart) Result.RESTART else Result.OK

View File

@ -473,7 +473,12 @@ class BaresipService: Service() {
stopMediaPlayer() stopMediaPlayer()
setCallVolume() setCallVolume()
proximitySensing(proximitySensing) proximitySensing(proximitySensing)
Api.ua_answer(uap, callp, Api.VIDMODE_OFF) if (telecom)
Api.ua_answer(uap, callp, Api.VIDMODE_OFF)
else {
Api.ua_answer(uap, callp, Api.VIDMODE_OFF)
ensureCommunicationMode()
}
} }
"Call Reject" -> { "Call Reject" -> {
@ -583,6 +588,18 @@ class BaresipService: Service() {
updateStatusNotification() updateStatusNotification()
} }
"Start Call" -> {
val uap = intent!!.getLongExtra("uap", 0L)
val uri = intent.getStringExtra("uri")!!
val conferenceCall = intent.getBooleanExtra("conferenceCall", false)
val onHoldCallp = intent.getLongExtra("onHoldCallp", 0L)
if (!requestAudioFocus(applicationContext)) {
toast(getString(R.string.audio_focus_denied))
return START_STICKY
}
runCall(uap, uri, conferenceCall, onHoldCallp)
}
"Stop" -> { "Stop" -> {
cleanService() cleanService()
if (isServiceRunning) { if (isServiceRunning) {
@ -782,12 +799,13 @@ class BaresipService: Service() {
"call outgoing" -> { "call outgoing" -> {
if (call!!.status.value == "transferring") if (call!!.status.value == "transferring")
break break
if (speakerPhoneAuto) { speakerPhone = speakerPhoneAuto
Log.d(TAG, "Auto-on speakerphone for outgoing call") // stopRinging()
speakerPhone = true
}
stopMediaPlayer() stopMediaPlayer()
setCallVolume() val hasTelecom = ConnectionService.connections.containsKey(callp) ||
ConnectionService.pendingOutgoingConnection != null
if (!hasTelecom)
setCallVolume()
ensureCommunicationMode() ensureCommunicationMode()
proximitySensing(proximitySensing) proximitySensing(proximitySensing)
} }
@ -808,10 +826,7 @@ class BaresipService: Service() {
return return
} }
"incoming call" -> { "incoming call" -> {
if (speakerPhoneAuto) { speakerPhone = speakerPhoneAuto
Log.d(TAG, "Auto-on speakerphone for incoming call")
speakerPhone = true
}
val peerUri = ev[1] val peerUri = ev[1]
val toastMsg = if (Call.isAnyCallActive(applicationContext)) val toastMsg = if (Call.isAnyCallActive(applicationContext))
String.format(getString(R.string.call_auto_rejected), String.format(getString(R.string.call_auto_rejected),
@ -866,14 +881,23 @@ class BaresipService: Service() {
Log.d(TAG, "Incoming call $uap/$callp/$peerUri") Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
if (Call.ofCallp(callp) == null) if (Call.ofCallp(callp) == null)
Call(callp, ua, peerUri, "in", "incoming").add() Call(callp, ua, peerUri, "in", "incoming").add()
val extras = android.os.Bundle() if (telecom) {
extras.putLong("uap", uap) val extras = android.os.Bundle()
extras.putLong("callp", callp) extras.putLong("uap", uap)
extras.putString("peerUri", peerUri) extras.putLong("callp", callp)
try { extras.putString("peerUri", peerUri)
tm.addNewIncomingCall(getPhoneAccountHandle(this), extras) try {
} catch (e: Exception) { tm.addNewIncomingCall(getPhoneAccountHandle(this), extras)
Log.e(TAG, "Telecom addNewIncomingCall failed: ${e.message}") } catch (e: Exception) {
Log.e(TAG, "Telecom addNewIncomingCall failed: ${e.message}")
}
} else {
if (!requestAudioFocus(applicationContext)) {
Log.w(TAG, "Audio focus denied for incoming call")
Api.ua_hangup(uap, callp, 486, "Busy Here")
return
}
handleIncomingCall(Call.ofCallp(callp)!!)
} }
return return
} }
@ -1069,8 +1093,13 @@ class BaresipService: Service() {
val tone = ev[2] val tone = ev[2]
if (tone == "busy") if (tone == "busy")
playBusy() playBusy()
else else {
if (!telecom && !Call.inCall())
abandonAudioFocus(applicationContext)
ensureCommunicationMode() ensureCommunicationMode()
}
if (!telecom && !Call.inCall())
abandonAudioFocus(applicationContext)
if (call.dir == "out") if (call.dir == "out")
call.rejected = call.startTime == null && call.rejected = call.startTime == null &&
!reason.startsWith("408") && !reason.startsWith("408") &&
@ -1398,16 +1427,22 @@ class BaresipService: Service() {
}, audioDelay) }, audioDelay)
} }
if (VERSION.SDK_INT < 31) { val isTelecom = Call.calls().any { ConnectionService.connections.containsKey(it.callp) } ||
ConnectionService.pendingOutgoingConnection != null
if (isTelecom)
executeCall()
else if (VERSION.SDK_INT < 31) {
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION") Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
am.mode = MODE_IN_COMMUNICATION am.mode = MODE_IN_COMMUNICATION
executeCall() executeCall()
} else { }
else {
val isAnyCallMode = am.mode == MODE_IN_COMMUNICATION || am.mode == AudioManager.MODE_IN_CALL val isAnyCallMode = am.mode == MODE_IN_COMMUNICATION || am.mode == AudioManager.MODE_IN_CALL
if (isAnyCallMode) { if (isAnyCallMode) {
Log.d(TAG, "Audio mode already in a call mode (${am.mode})") Log.d(TAG, "Audio mode already in a call mode (${am.mode})")
executeCall() executeCall()
} else { }
else {
audioModeChangedListener = AudioManager.OnModeChangedListener { mode -> audioModeChangedListener = AudioManager.OnModeChangedListener { mode ->
if (mode == MODE_IN_COMMUNICATION || mode == AudioManager.MODE_IN_CALL) { if (mode == MODE_IN_COMMUNICATION || mode == AudioManager.MODE_IN_CALL) {
Log.d(TAG, "Audio mode changed to $mode") Log.d(TAG, "Audio mode changed to $mode")
@ -1946,8 +1981,11 @@ class BaresipService: Service() {
cleanupRunnable = null cleanupRunnable = null
} }
if (isSpeakerphoneOn == speakerPhone) { if (isSpeakerphoneOn == speakerPhone) {
Log.d(TAG, "Already in valid call mode ($currentMode) with correct speaker state.") val hasTelecom = Call.calls().any { ConnectionService.connections.containsKey(it.callp) }
return if (hasTelecom || currentMode == MODE_IN_COMMUNICATION) {
Log.d(TAG, "Already in valid call mode ($currentMode) with correct speaker state.")
return
}
} }
} else if (!Call.inCall() && currentMode == MODE_NORMAL) { } else if (!Call.inCall() && currentMode == MODE_NORMAL) {
if (speakerPhone) { if (speakerPhone) {
@ -1971,12 +2009,13 @@ class BaresipService: Service() {
val runnable = Runnable { val runnable = Runnable {
cleanupRunnable = null cleanupRunnable = null
if (Call.inCall()) { if (Call.inCall()) {
if (am.mode != MODE_IN_COMMUNICATION && am.mode != AudioManager.MODE_IN_CALL) { val hasTelecom = Call.calls().any { ConnectionService.connections.containsKey(it.callp) }
if (!hasTelecom && am.mode != MODE_IN_COMMUNICATION && am.mode != AudioManager.MODE_IN_CALL) {
am.mode = MODE_IN_COMMUNICATION am.mode = MODE_IN_COMMUNICATION
Log.d(TAG, "Manual Mode Guard: Setting MODE_IN_COMMUNICATON from ${am.mode}") Log.d(TAG, "Manual Mode Guard: Setting MODE_IN_COMMUNICATON from ${am.mode}")
} }
Log.d(TAG, "Applying speakerphone state: $speakerPhone") Log.d(TAG, "Applying speakerphone state: $speakerPhone")
if (!Call.calls().any { ConnectionService.connections.containsKey(it.callp) }) { if (!hasTelecom) {
Log.d(TAG, "No Telecom connection, using AudioManager for speaker") Log.d(TAG, "No Telecom connection, using AudioManager for speaker")
Utils.setSpeakerPhone(mainExecutor, am, speakerPhone) Utils.setSpeakerPhone(mainExecutor, am, speakerPhone)
} else { } else {
@ -2011,7 +2050,8 @@ class BaresipService: Service() {
) )
) )
} }
resetCallVolume() if (!Call.calls().any { ConnectionService.connections.containsKey(it.callp) })
resetCallVolume()
proximitySensing(false) proximitySensing(false)
} }
} }
@ -2264,6 +2304,7 @@ class BaresipService: Service() {
var isRecOn = false var isRecOn = false
var toneCountry = "us" var toneCountry = "us"
var proximitySensing = true var proximitySensing = true
var telecom = true
val uas = mutableStateOf(emptyList<UserAgent>()) val uas = mutableStateOf(emptyList<UserAgent>())
val uasStatus = mutableStateOf(emptyMap<String, Int>()) val uasStatus = mutableStateOf(emptyMap<String, Int>())
@ -2297,6 +2338,7 @@ class BaresipService: Service() {
private var agc: AutomaticGainControl? = null private var agc: AutomaticGainControl? = null
private var ns: NoiseSuppressor? = null private var ns: NoiseSuppressor? = null
private var recorderSessionId = 0 private var recorderSessionId = 0
private var audioFocusRequest: androidx.media.AudioFocusRequestCompat? = null
var rt: Ringtone? = null var rt: Ringtone? = null
@ -2325,5 +2367,52 @@ class BaresipService: Service() {
Log.d(TAG, "Added service event ${event.event}") Log.d(TAG, "Added service event ${event.event}")
} }
} }
fun requestAudioFocus(ctx: Context): Boolean {
Log.d(TAG, "Requesting audio focus")
if (audioFocusRequest != null) {
Log.d(TAG, "Already focused")
return true
}
val am = ctx.getSystemService(AUDIO_SERVICE) as AudioManager
val attributes = androidx.media.AudioAttributesCompat.Builder()
.setUsage(androidx.media.AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
.setContentType(androidx.media.AudioAttributesCompat.CONTENT_TYPE_SPEECH)
.build()
audioFocusRequest =
androidx.media.AudioFocusRequestCompat.Builder(androidx.media.AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(attributes)
.setOnAudioFocusChangeListener { }
.build()
if (androidx.media.AudioManagerCompat.requestAudioFocus(
am,
audioFocusRequest!!
) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
) {
Log.d(TAG, "requestAudioFocus granted")
} else {
Log.w(TAG, "requestAudioFocus denied")
audioFocusRequest = null
}
return audioFocusRequest != null
}
fun abandonAudioFocus(ctx: Context) {
val am = ctx.getSystemService(AUDIO_SERVICE) as AudioManager
if (audioFocusRequest != null) {
Log.d(TAG, "Abandoning audio focus")
if (androidx.media.AudioManagerCompat.abandonAudioFocusRequest(
am,
audioFocusRequest!!
) ==
AudioManager.AUDIOFOCUS_REQUEST_GRANTED
) {
audioFocusRequest = null
} else {
Log.e(TAG, "Failed to abandon audio focus")
}
}
am.mode = MODE_NORMAL
}
} }
} }

View File

@ -224,6 +224,13 @@ object Config {
BaresipService.toneCountry = toneCountry BaresipService.toneCountry = toneCountry
config = "${config}tone_country ${BaresipService.toneCountry}\n" config = "${config}tone_country ${BaresipService.toneCountry}\n"
val telecom = previousVariable("telecom")
if (telecom != "")
BaresipService.telecom = telecom == "yes"
else
BaresipService.telecom = true
config = "${config}telecom ${if (BaresipService.telecom) "yes" else "no"}\n"
save() save()
BaresipService.isConfigInitialized = true BaresipService.isConfigInitialized = true

View File

@ -1505,10 +1505,15 @@ private fun CallRow(
onClick = { onClick = {
if (call.terminated.value) return@IconButton if (call.terminated.value) return@IconButton
call.terminated.value = true call.terminated.value = true
val connection = ConnectionService.connections[call.callp] if (BaresipService.telecom) {
if (connection != null) val connection = ConnectionService.connections[call.callp]
connection.onDisconnect() if (connection != null)
else { connection.onDisconnect()
else {
Log.d(TAG, "AoR ${call.ua.account.aor} canceling call ${call.callp}")
Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated")
}
} else {
Log.d(TAG, "AoR ${call.ua.account.aor} canceling call ${call.callp}") Log.d(TAG, "AoR ${call.ua.account.aor} canceling call ${call.callp}")
Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated") Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated")
} }
@ -1532,10 +1537,15 @@ private fun CallRow(
onClick = { onClick = {
if (call.terminated.value) return@IconButton if (call.terminated.value) return@IconButton
call.terminated.value = true call.terminated.value = true
val connection = ConnectionService.connections[call.callp] if (BaresipService.telecom) {
if (connection != null) val connection = ConnectionService.connections[call.callp]
connection.onDisconnect() if (connection != null)
else { connection.onDisconnect()
else {
Log.d(TAG, "AoR ${call.ua.account.aor} hanging up call ${call.callp}")
Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated")
}
} else {
Log.d(TAG, "AoR ${call.ua.account.aor} hanging up call ${call.callp}") Log.d(TAG, "AoR ${call.ua.account.aor} hanging up call ${call.callp}")
Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated") Api.ua_hangup(call.ua.uap, call.callp, 487, "Request Terminated")
} }
@ -2074,28 +2084,40 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, confer
!Call.calls().any { it.ua.account.aor == ua.account.aor }) !Call.calls().any { it.ua.account.aor == ua.account.aor })
Toast.makeText(ctx, R.string.call_already_active, Toast.LENGTH_SHORT).show() Toast.makeText(ctx, R.string.call_already_active, Toast.LENGTH_SHORT).show()
else { else {
val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as android.telecom.TelecomManager if (BaresipService.telecom) {
val extras = android.os.Bundle() val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as android.telecom.TelecomManager
extras.putParcelable(android.telecom.TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, val extras = android.os.Bundle()
BaresipService.getPhoneAccountHandle(ctx)) extras.putParcelable(android.telecom.TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
val callExtras = android.os.Bundle() BaresipService.getPhoneAccountHandle(ctx))
callExtras.putBoolean("conferenceCall", conferenceCall) val callExtras = android.os.Bundle()
callExtras.putLong("uap", ua.uap) callExtras.putBoolean("conferenceCall", conferenceCall)
if (onHoldCallp != 0L) callExtras.putLong("uap", ua.uap)
callExtras.putLong("onHoldCallp", onHoldCallp) if (onHoldCallp != 0L)
extras.putBundle(android.telecom.TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras) callExtras.putLong("onHoldCallp", onHoldCallp)
try { extras.putBundle(android.telecom.TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
Log.d(TAG, "Placing Telecom call to $uri with uap=${ua.uap}") try {
tm.placeCall(uri.toUri(), extras) Log.d(TAG, "Placing Telecom call to $uri with uap=${ua.uap}")
} catch (e: SecurityException) { tm.placeCall(uri.toUri(), extras)
Log.e(TAG, "placeCall failed: ${e.message}") } catch (e: SecurityException) {
Log.e(TAG, "placeCall failed: ${e.message}")
}
}
else {
val intent = Intent(ctx, BaresipService::class.java)
intent.action = "Start Call"
intent.putExtra("uap", ua.uap)
intent.putExtra("uri", uri)
intent.putExtra("conferenceCall", conferenceCall)
intent.putExtra("onHoldCallp", onHoldCallp)
ctx.startService(intent)
} }
} }
} }
private fun answer(ctx: Context, call: Call) { private fun answer(ctx: Context, call: Call) {
Log.d(TAG, "AoR ${call.ua.account.aor} answering call from ${call.callUri.value}") Log.d(TAG, "AoR ${call.ua.account.aor} answering call from ${call.callUri.value}")
ConnectionService.connections[call.callp]?.setActive() if (BaresipService.telecom)
ConnectionService.connections[call.callp]?.setActive()
val intent = Intent(ctx, BaresipService::class.java) val intent = Intent(ctx, BaresipService::class.java)
intent.action = "Call Answer" intent.action = "Call Answer"
intent.putExtra("uap", call.ua.uap) intent.putExtra("uap", call.ua.uap)
@ -2105,9 +2127,15 @@ private fun answer(ctx: Context, call: Call) {
private fun reject(call: Call) { private fun reject(call: Call) {
Log.d(TAG, "AoR ${call.ua.account.aor} rejecting call ${call.callp} from ${call.callUri.value}") Log.d(TAG, "AoR ${call.ua.account.aor} rejecting call ${call.callp} from ${call.callUri.value}")
val connection = ConnectionService.connections[call.callp] if (BaresipService.telecom) {
if (connection != null) val connection = ConnectionService.connections[call.callp]
connection.onReject() if (connection != null)
connection.onReject()
else {
call.rejected = true
Api.ua_hangup(call.ua.uap, call.callp, 486, "Busy Here")
}
}
else { else {
call.rejected = true call.rejected = true
Api.ua_hangup(call.ua.uap, call.callp, 486, "Busy Here") Api.ua_hangup(call.ua.uap, call.callp, 486, "Busy Here")
@ -2323,6 +2351,8 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
when (ev[0]) { when (ev[0]) {
"call rejected" -> { "call rejected" -> {
if (!BaresipService.telecom)
BaresipService.abandonAudioFocus(ctx)
if (aor == viewModel.selectedAor.value) if (aor == viewModel.selectedAor.value)
viewModel.triggerAccountUpdate() viewModel.triggerAccountUpdate()
} }

View File

@ -435,6 +435,8 @@
<string name="proximity_sensing">Läheisyyden tunnistus</string> <string name="proximity_sensing">Läheisyyden tunnistus</string>
<string name="proximity_sensing_help">Jos merkitty, läheisyyden tunnistus on aktiivinen <string name="proximity_sensing_help">Jos merkitty, läheisyyden tunnistus on aktiivinen
puhelun aikana.</string> puhelun aikana.</string>
<string name="telecom">Telecom-kehys</string>
<string name="telecom_help">Käytä Android Telecom -kehystä puheluihin. Poista valinta, jos vastapuoli ei kuule ääntäsi tai jos sinulla on ongelmia Bluetooth-kuulokkeiden kanssa.</string>
<string name="video_size">Videon kehyskoko</string> <string name="video_size">Videon kehyskoko</string>
<string name="video_size_help">Lähetettävän videon kehyskoko (leveys x korkeus)</string> <string name="video_size_help">Lähetettävän videon kehyskoko (leveys x korkeus)</string>
<string name="video_fps">Videokehysten lähetystaajuus</string> <string name="video_fps">Videokehysten lähetystaajuus</string>

View File

@ -406,8 +406,10 @@
<string name="dynamic_colors_help">Use dynamic colors if enabled in Android Settings</string> <string name="dynamic_colors_help">Use dynamic colors if enabled in Android Settings</string>
<string name="colorblind">Colorblind</string> <string name="colorblind">Colorblind</string>
<string name="colorblind_help">Use colorblind friendly registration status icons</string> <string name="colorblind_help">Use colorblind friendly registration status icons</string>
<string name="proximity_sensing">Proximity Sensing</string>"> <string name="proximity_sensing">Proximity Sensing</string>
<string name="proximity_sensing_help">If checked, proximity sensing is active during calls.</string> <string name="proximity_sensing_help">If checked, proximity sensing is active during calls.</string>
<string name="telecom">Telecom Framework</string>
<string name="telecom_help">Use Android Telecom framework for calls. Uncheck this if peer does not hear your audio or if you have issues with Bluetooth headsets.</string>
<string name="video_size">Video Frame Size</string> <string name="video_size">Video Frame Size</string>
<string name="video_size_help">Size of transmitted video frames (width x height)</string> <string name="video_size_help">Size of transmitted video frames (width x height)</string>
<string name="video_fps">Video Frames Per Second</string> <string name="video_fps">Video Frames Per Second</string>