diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 121cac74..203662fc 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -886,36 +886,45 @@ class BaresipService: Service() { return } "call update" -> { - val newHeldState = when (ev[1].toInt()) { - Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true + val newHeldState = when (ev[1].toInt()) {Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true else -> false } val connection = ConnectionService.connections[callp] + // Handle Remote Un-hold 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 + Log.d(TAG, "Call ${call.callp} un-held by peer.") - // Tell Android to make this call active. - // Telecom will automatically trigger onHold for other connections. - connection?.setActive() + // Clear our local manual hold flag so resume() can execute + call.onhold = false + + // 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 if (newHeldState) { - connection?.setOnHold() - call.callOnHold.value = true + // Peer put us on hold 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.callOnHold.value = true + connection?.setOnHold() + } else { + // Peer un-held us 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) { @@ -926,6 +935,15 @@ class BaresipService: Service() { 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") return } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Call.kt b/app/src/main/kotlin/com/tutpro/baresip/Call.kt index 10036eb9..cc1ab3d3 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Call.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Call.kt @@ -59,38 +59,43 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str fun hold(): Boolean { 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 + // 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 { - 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 + if (!onhold && !held) return true + + // 1. Hold other calls first + for (c in BaresipService.calls) { + if (c.callp != this.callp && !c.onhold && !c.held) { + Log.d("Baresip", "Auto-holding active call ${c.callp}") + c.hold() + } } - // Fallback if no Telecom connection exists + + val connection = ConnectionService.connections[callp] + + // 2. SIP Signaling if (Api.call_hold(callp, false)) { onhold = false callOnHold.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 { diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt b/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt index 948a090a..686bd8a2 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt @@ -201,26 +201,40 @@ class ConnectionService : ConnectionService() { } override fun onHold() { - 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) + // Do NOT call super.onHold() first, as it might change internal state + // before we can grab the call object. + Log.d(TAG, "Telecom Connection onHold $callp") + val call = Call.ofCallp(callp) + if (call != null && !call.conferenceCall) { + // 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() { Log.d(TAG, "Telecom Connection onUnhold $callp") - val call = BaresipService.calls.find { it.callp == this.callp } - if (call != null) { - if (!call.conferenceCall) { + val call = Call.ofCallp(callp) + if (call != null && !call.conferenceCall) { + // 1. Force SIP Signaling + if (Api.call_hold(call.callp, false)) { + // 2. Sync Call object state call.onhold = false call.callOnHold.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() } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt index c34ada70..474c159a 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt @@ -1538,33 +1538,14 @@ private fun CallRow( } if (!call.conferenceCall) - IconButton( - modifier = Modifier.size(48.dp), + IconButton( modifier = Modifier.size(48.dp), onClick = { - val connection = ConnectionService.connections[call.callp] if (call.callOnHold.value) { - if (!Call.isAnyCallActive(ctx)) { - Log.d( - TAG, - "AoR ${call.ua.account.aor} resuming call ${call.callp} with ${call.callUri.value}" - ) - 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 + Log.d(TAG, "User requested resume for ${call.callp}") + call.resume() // This now automatically holds other calls + } else { + Log.d(TAG, "User requested hold for ${call.callp}") + call.hold() } }, ) {