Allow switching between active call and call on-hold

This commit is contained in:
Juha Heinanen
2026-04-05 09:16:51 +03:00
parent 9a19cce1e6
commit 6c2419dd05
4 changed files with 89 additions and 71 deletions
@@ -886,36 +886,45 @@ class BaresipService: Service() {
return return
} }
"call update" -> { "call update" -> {
val newHeldState = when (ev[1].toInt()) { val newHeldState = when (ev[1].toInt()) {Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true
Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true
else -> false else -> false
} }
val connection = ConnectionService.connections[callp] val connection = ConnectionService.connections[callp]
// Handle Remote Un-hold
if (call!!.held && !newHeldState) { if (call!!.held && !newHeldState) {
Log.d(TAG, "Call ${call.callp} un-held by peer. Requesting Telecom Active.") Log.d(TAG, "Call ${call.callp} un-held by peer.")
// Clear local UI state
call.onhold = false
call.callOnHold.value = false
call.showOnHoldNotice.value = false
// Tell Android to make this call active. // Clear our local manual hold flag so resume() can execute
// Telecom will automatically trigger onHold for other connections. call.onhold = false
connection?.setActive()
// We use a Coroutine with a small delay to let the SIP
// transaction (the re-INVITE from the peer) finish
// before we try to hold the other call and resume this one.
CoroutineScope(Dispatchers.Main).launch {
delay(100)
call.resume()
}
} }
call.held = newHeldState call.held = newHeldState
if (newHeldState) { if (newHeldState) {
connection?.setOnHold() // Peer put us on hold
call.callOnHold.value = true
call.showOnHoldNotice.value = true call.showOnHoldNotice.value = true
} else if (!call.onhold) { call.callOnHold.value = true
// Only set active if we haven't manually put it on hold ourselves connection?.setOnHold()
call.callOnHold.value = false } else {
// Peer un-held us
call.showOnHoldNotice.value = false call.showOnHoldNotice.value = false
connection?.setActive()
// Only clear the UI if we aren't also manually holding it
// (If we just called call.resume() above, it will handle this)
if (!call.onhold) {
call.callOnHold.value = false
connection?.setActive()
}
} }
if (call.state() == Api.CALL_STATE_EARLY) { if (call.state() == Api.CALL_STATE_EARLY) {
@@ -926,6 +935,15 @@ class BaresipService: Service() {
playRingBack() playRingBack()
} }
} }
if (call.status.value == "connected" && !call.held && !call.onhold) {
if (call.callOnHold.value || call.showOnHoldNotice.value) {
Log.d(TAG, "Safety guard: Clearing stuck hold flags for ${call.callp}")
call.callOnHold.value = false
call.showOnHoldNotice.value = false
connection?.setActive()
}
}
if (!isMainVisible || call.status.value != "connected") if (!isMainVisible || call.status.value != "connected")
return return
} }
+24 -19
View File
@@ -59,38 +59,43 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
fun hold(): Boolean { fun hold(): Boolean {
if (onhold) return true 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)) { if (Api.call_hold(callp, true)) {
onhold = true onhold = true
callOnHold.value = true callOnHold.value = true
showOnHoldNotice.value = true // Fix: Do NOT set showOnHoldNotice to true here.
// That notice is for when the PEER holds us.
showOnHoldNotice.value = false
ConnectionService.connections[callp]?.setOnHold()
return true
} }
return onhold return false
} }
fun resume(): Boolean { fun resume(): Boolean {
if (!onhold) return true if (!onhold && !held) return true
val connection = ConnectionService.connections[callp]
if (connection != null) { // 1. Hold other calls first
connection.setAddress( for (c in BaresipService.calls) {
"sip:$peerUri".toUri(), if (c.callp != this.callp && !c.onhold && !c.held) {
android.telecom.TelecomManager.PRESENTATION_ALLOWED Log.d("Baresip", "Auto-holding active call ${c.callp}")
) c.hold()
connection.setActive() }
return true
} }
// Fallback if no Telecom connection exists
val connection = ConnectionService.connections[callp]
// 2. SIP Signaling
if (Api.call_hold(callp, false)) { if (Api.call_hold(callp, false)) {
onhold = false onhold = false
callOnHold.value = false callOnHold.value = false
showOnHoldNotice.value = false showOnHoldNotice.value = false
// 3. Telecom Sync
connection?.setAddress("sip:$peerUri".toUri(), android.telecom.TelecomManager.PRESENTATION_ALLOWED)
connection?.setActive()
return true
} }
return !onhold return false
} }
fun transfer(uri: String): Boolean { fun transfer(uri: String): Boolean {
@@ -201,26 +201,40 @@ class ConnectionService : ConnectionService() {
} }
override fun onHold() { override fun onHold() {
super.onHold() // Do NOT call super.onHold() first, as it might change internal state
Log.d(TAG, "Telecom requested Hold for $callp") // before we can grab the call object.
val call = BaresipService.calls.find { it.callp == this.callp } Log.d(TAG, "Telecom Connection onHold $callp")
if (call != null && !call.onhold && !call.conferenceCall) { val call = Call.ofCallp(callp)
call.onhold = true if (call != null && !call.conferenceCall) {
Api.call_hold(call.callp, true) // 1. Force SIP Signaling
if (Api.call_hold(call.callp, true)) {
// 2. Sync Call object state
call.onhold = true
call.callOnHold.value = true
call.showOnHoldNotice.value = true
// 3. Tell Telecom the move is complete
setOnHold()
} else {
Log.e(TAG, "SIP Hold failed for $callp")
}
} }
} }
override fun onUnhold() { override fun onUnhold() {
Log.d(TAG, "Telecom Connection onUnhold $callp") Log.d(TAG, "Telecom Connection onUnhold $callp")
val call = BaresipService.calls.find { it.callp == this.callp } val call = Call.ofCallp(callp)
if (call != null) { if (call != null && !call.conferenceCall) {
if (!call.conferenceCall) { // 1. Force SIP Signaling
if (Api.call_hold(call.callp, false)) {
// 2. Sync Call object state
call.onhold = false call.onhold = false
call.callOnHold.value = false call.callOnHold.value = false
call.showOnHoldNotice.value = false call.showOnHoldNotice.value = false
Api.call_hold(call.callp, false) // 3. Tell Telecom we are active
setActive()
} else {
Log.e(TAG, "SIP Resume failed for $callp")
} }
setActive()
} }
} }
@@ -1538,33 +1538,14 @@ private fun CallRow(
} }
if (!call.conferenceCall) if (!call.conferenceCall)
IconButton( IconButton( modifier = Modifier.size(48.dp),
modifier = Modifier.size(48.dp),
onClick = { onClick = {
val connection = ConnectionService.connections[call.callp]
if (call.callOnHold.value) { if (call.callOnHold.value) {
if (!Call.isAnyCallActive(ctx)) { Log.d(TAG, "User requested resume for ${call.callp}")
Log.d( call.resume() // This now automatically holds other calls
TAG, } else {
"AoR ${call.ua.account.aor} resuming call ${call.callp} with ${call.callUri.value}" Log.d(TAG, "User requested hold for ${call.callp}")
) call.hold()
if (connection != null)
connection.onUnhold()
else
call.resume()
call.callOnHold.value = false
}
}
else {
Log.d(
TAG,
"AoR ${call.ua.account.aor} holding call ${call.callp} with ${call.callUri.value}"
)
if (connection != null)
connection.onHold()
else
call.hold()
call.callOnHold.value = true
} }
}, },
) { ) {