Call hold/unhold improvements for multiple simultaneous calls

This commit is contained in:
Juha Heinanen
2026-04-04 19:05:56 +03:00
parent f1ad2af9f4
commit 9a19cce1e6
6 changed files with 95 additions and 33 deletions

View File

@ -1384,7 +1384,7 @@ JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1start_1audio(
re_thread_leave();
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1hold(
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1hold(
JNIEnv *env, jobject obj, jlong call, jboolean hold)
{
(void)env;
@ -1403,7 +1403,7 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1hold(
}
if (err)
LOGW("call_hold error: %d\n", err);
return err;
return err == 0;
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1ismuted(

View File

@ -90,7 +90,7 @@ object Api {
external fun bevent_stop(event: Long)
external fun call_connect(callp: Long, peer_uri: String): Int
external fun call_hold(callp: Long, hold: Boolean): Int
external fun call_hold(callp: Long, hold: Boolean): Boolean
@Suppress("unused")
external fun call_ismuted(callp: Long): Boolean
external fun call_transfer(callp: Long, peer_uri: String): Int

View File

@ -851,10 +851,8 @@ class BaresipService: Service() {
"call incoming" -> {
val peerUri = ev[1]
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
if (Call.ofCallp(callp) == null)
Call(callp, ua, peerUri, "in", "incoming").add()
val extras = android.os.Bundle()
extras.putLong("uap", uap)
extras.putLong("callp", callp)
@ -888,13 +886,38 @@ class BaresipService: Service() {
return
}
"call update" -> {
val held = when (ev[1].toInt()) {
val newHeldState = when (ev[1].toInt()) {
Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true
else -> false
}
call!!.held = held
val connection = ConnectionService.connections[callp]
if (held) connection?.setOnHold() else connection?.setActive()
if (call!!.held && !newHeldState) {
Log.d(TAG, "Call ${call.callp} un-held by peer. Requesting Telecom Active.")
// Clear local UI state
call.onhold = false
call.callOnHold.value = false
call.showOnHoldNotice.value = false
// Tell Android to make this call active.
// Telecom will automatically trigger onHold for other connections.
connection?.setActive()
}
call.held = newHeldState
if (newHeldState) {
connection?.setOnHold()
call.callOnHold.value = true
call.showOnHoldNotice.value = true
} else if (!call.onhold) {
// Only set active if we haven't manually put it on hold ourselves
call.callOnHold.value = false
call.showOnHoldNotice.value = false
connection?.setActive()
}
if (call.state() == Api.CALL_STATE_EARLY) {
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
stopMediaPlayer()

View File

@ -5,6 +5,7 @@ import android.media.AudioManager
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.core.net.toUri
import java.util.*
class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: String, initialStatus: String) {
@ -57,20 +58,43 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
}
fun hold(): Boolean {
if (Api.call_hold(callp, true) == 0)
if (onhold) return true
val connection = ConnectionService.connections[callp]
if (connection != null) {
connection.setOnHold()
return true
}
// Fallback if no Telecom connection exists
if (Api.call_hold(callp, true)) {
onhold = true
callOnHold.value = true
showOnHoldNotice.value = true
}
return onhold
}
fun resume(): Boolean {
if (Api.call_hold(callp, false) == 0)
if (!onhold) return true
val connection = ConnectionService.connections[callp]
if (connection != null) {
connection.setAddress(
"sip:$peerUri".toUri(),
android.telecom.TelecomManager.PRESENTATION_ALLOWED
)
connection.setActive()
return true
}
// Fallback if no Telecom connection exists
if (Api.call_hold(callp, false)) {
onhold = false
callOnHold.value = false
showOnHoldNotice.value = false
}
return !onhold
}
fun transfer(uri: String): Boolean {
val err = Api.call_hold(callp, true)
if (err != 0)
if (!Api.call_hold(callp, true))
return false
onhold = true
referTo = uri
@ -79,7 +103,7 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
fun executeTransfer(): Boolean {
return if (onHoldCall != null) {
if (Api.call_hold(callp, true) == 0)
if (Api.call_hold(callp, true))
Api.call_replace_transfer(onHoldCall!!.callp, callp)
else
false

View File

@ -201,19 +201,27 @@ class ConnectionService : ConnectionService() {
}
override fun onHold() {
Log.d(TAG, "Telecom Connection onHold $callp")
val c = Call.ofCallp(callp)
if (c?.conferenceCall != true)
c?.hold()
setOnHold()
super.onHold()
Log.d(TAG, "Telecom requested Hold for $callp")
val call = BaresipService.calls.find { it.callp == this.callp }
if (call != null && !call.onhold && !call.conferenceCall) {
call.onhold = true
Api.call_hold(call.callp, true)
}
}
override fun onUnhold() {
Log.d(TAG, "Telecom Connection onUnhold $callp")
val c = Call.ofCallp(callp)
if (c?.conferenceCall != true)
c?.resume()
setActive()
val call = BaresipService.calls.find { it.callp == this.callp }
if (call != null) {
if (!call.conferenceCall) {
call.onhold = false
call.callOnHold.value = false
call.showOnHoldNotice.value = false
Api.call_hold(call.callp, false)
}
setActive()
}
}
override fun onPlayDtmfTone(c: Char) {

View File

@ -108,6 +108,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -649,7 +650,8 @@ private fun TopAppBar(
val aor = viewModel.selectedAor.value
val ua = uas.value.find { it.account.aor == aor }
val call = ua?.currentCall()
val connection = if (call != null) ConnectionService.connections[call.callp] else null
val connection =
if (call != null) ConnectionService.connections[call.callp] else null
if (connection != null) {
@Suppress("DEPRECATION")
connection.setAudioRoute(
@ -892,9 +894,9 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
val calls by viewModel.calls.collectAsState()
val selectedAor by viewModel.selectedAor.collectAsState()
val filteredCalls = calls.filter { it.ua.account.aor == selectedAor && it.status.value != "disconnecting" }
val dialingOrRinging = filteredCalls.any { it.status.value == "outgoing" || it.status.value == "incoming" }
val conferenceCall = filteredCalls.any { it.conferenceCall }
val aorCalls = calls.filter { it.ua.account.aor == selectedAor }
val hasActiveCalls = aorCalls.any { !it.callOnHold.value }
val conferenceCall = aorCalls.any { it.conferenceCall }
LaunchedEffect(isRefreshing) {
if (isRefreshing) {
@ -997,12 +999,13 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
) {
AccountSpinner(ctx, viewModel, navController)
filteredCalls.forEach { call ->
CallCard(ctx = ctx, viewModel = viewModel, call = call, dialerState = null)
aorCalls.forEach { call ->
key(call.callp) {
CallCard(ctx = ctx, viewModel = viewModel, call = call, dialerState = null)
}
}
// Only show the dialer if we are not in a transient state
if (!dialingOrRinging && (filteredCalls.isEmpty() || conferenceCall))
if (!hasActiveCalls || conferenceCall)
CallCard(ctx = ctx, viewModel = viewModel, call = null, dialerState = viewModel.dialerState)
Indicator(
@ -1539,7 +1542,7 @@ private fun CallRow(
modifier = Modifier.size(48.dp),
onClick = {
val connection = ConnectionService.connections[call.callp]
if (call.onhold) {
if (call.callOnHold.value) {
if (!Call.isAnyCallActive(ctx)) {
Log.d(
TAG,
@ -1549,6 +1552,7 @@ private fun CallRow(
connection.onUnhold()
else
call.resume()
call.callOnHold.value = false
}
}
else {
@ -1560,6 +1564,7 @@ private fun CallRow(
connection.onHold()
else
call.hold()
call.callOnHold.value = true
}
},
) {
@ -1728,8 +1733,10 @@ private fun CallRow(
modifier = Modifier
.fillMaxWidth()
.clickable {
transferUri = suggestion.toString()
call.showSuggestions.value = false
transferUri =
suggestion.toString()
call.showSuggestions.value =
false
}
.padding(12.dp)
) {