Call hold/unhold improvements for multiple simultaneous calls
This commit is contained in:
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user