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();
|
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)
|
JNIEnv *env, jobject obj, jlong call, jboolean hold)
|
||||||
{
|
{
|
||||||
(void)env;
|
(void)env;
|
||||||
@@ -1403,7 +1403,7 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1hold(
|
|||||||
}
|
}
|
||||||
if (err)
|
if (err)
|
||||||
LOGW("call_hold error: %d\n", err);
|
LOGW("call_hold error: %d\n", err);
|
||||||
return err;
|
return err == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1ismuted(
|
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1ismuted(
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ object Api {
|
|||||||
external fun bevent_stop(event: Long)
|
external fun bevent_stop(event: Long)
|
||||||
|
|
||||||
external fun call_connect(callp: Long, peer_uri: String): Int
|
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")
|
@Suppress("unused")
|
||||||
external fun call_ismuted(callp: Long): Boolean
|
external fun call_ismuted(callp: Long): Boolean
|
||||||
external fun call_transfer(callp: Long, peer_uri: String): Int
|
external fun call_transfer(callp: Long, peer_uri: String): Int
|
||||||
|
|||||||
@@ -851,10 +851,8 @@ class BaresipService: Service() {
|
|||||||
"call incoming" -> {
|
"call incoming" -> {
|
||||||
val peerUri = ev[1]
|
val peerUri = ev[1]
|
||||||
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()
|
val extras = android.os.Bundle()
|
||||||
extras.putLong("uap", uap)
|
extras.putLong("uap", uap)
|
||||||
extras.putLong("callp", callp)
|
extras.putLong("callp", callp)
|
||||||
@@ -888,13 +886,38 @@ class BaresipService: Service() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
"call update" -> {
|
"call update" -> {
|
||||||
val held = 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
|
||||||
}
|
}
|
||||||
call!!.held = held
|
|
||||||
val connection = ConnectionService.connections[callp]
|
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 (call.state() == Api.CALL_STATE_EARLY) {
|
||||||
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
|
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
|
||||||
stopMediaPlayer()
|
stopMediaPlayer()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.media.AudioManager
|
|||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.core.net.toUri
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: String, initialStatus: String) {
|
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 {
|
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
|
onhold = true
|
||||||
|
callOnHold.value = true
|
||||||
|
showOnHoldNotice.value = true
|
||||||
|
}
|
||||||
return onhold
|
return onhold
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume(): Boolean {
|
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
|
onhold = false
|
||||||
|
callOnHold.value = false
|
||||||
|
showOnHoldNotice.value = false
|
||||||
|
}
|
||||||
return !onhold
|
return !onhold
|
||||||
}
|
}
|
||||||
|
|
||||||
fun transfer(uri: String): Boolean {
|
fun transfer(uri: String): Boolean {
|
||||||
val err = Api.call_hold(callp, true)
|
if (!Api.call_hold(callp, true))
|
||||||
if (err != 0)
|
|
||||||
return false
|
return false
|
||||||
onhold = true
|
onhold = true
|
||||||
referTo = uri
|
referTo = uri
|
||||||
@@ -79,7 +103,7 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
|||||||
|
|
||||||
fun executeTransfer(): Boolean {
|
fun executeTransfer(): Boolean {
|
||||||
return if (onHoldCall != null) {
|
return if (onHoldCall != null) {
|
||||||
if (Api.call_hold(callp, true) == 0)
|
if (Api.call_hold(callp, true))
|
||||||
Api.call_replace_transfer(onHoldCall!!.callp, callp)
|
Api.call_replace_transfer(onHoldCall!!.callp, callp)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -201,19 +201,27 @@ class ConnectionService : ConnectionService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onHold() {
|
override fun onHold() {
|
||||||
Log.d(TAG, "Telecom Connection onHold $callp")
|
super.onHold()
|
||||||
val c = Call.ofCallp(callp)
|
Log.d(TAG, "Telecom requested Hold for $callp")
|
||||||
if (c?.conferenceCall != true)
|
val call = BaresipService.calls.find { it.callp == this.callp }
|
||||||
c?.hold()
|
if (call != null && !call.onhold && !call.conferenceCall) {
|
||||||
setOnHold()
|
call.onhold = true
|
||||||
|
Api.call_hold(call.callp, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnhold() {
|
override fun onUnhold() {
|
||||||
Log.d(TAG, "Telecom Connection onUnhold $callp")
|
Log.d(TAG, "Telecom Connection onUnhold $callp")
|
||||||
val c = Call.ofCallp(callp)
|
val call = BaresipService.calls.find { it.callp == this.callp }
|
||||||
if (c?.conferenceCall != true)
|
if (call != null) {
|
||||||
c?.resume()
|
if (!call.conferenceCall) {
|
||||||
setActive()
|
call.onhold = false
|
||||||
|
call.callOnHold.value = false
|
||||||
|
call.showOnHoldNotice.value = false
|
||||||
|
Api.call_hold(call.callp, false)
|
||||||
|
}
|
||||||
|
setActive()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayDtmfTone(c: Char) {
|
override fun onPlayDtmfTone(c: Char) {
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -649,7 +650,8 @@ private fun TopAppBar(
|
|||||||
val aor = viewModel.selectedAor.value
|
val aor = viewModel.selectedAor.value
|
||||||
val ua = uas.value.find { it.account.aor == aor }
|
val ua = uas.value.find { it.account.aor == aor }
|
||||||
val call = ua?.currentCall()
|
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) {
|
if (connection != null) {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
connection.setAudioRoute(
|
connection.setAudioRoute(
|
||||||
@@ -892,9 +894,9 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
|
|
||||||
val calls by viewModel.calls.collectAsState()
|
val calls by viewModel.calls.collectAsState()
|
||||||
val selectedAor by viewModel.selectedAor.collectAsState()
|
val selectedAor by viewModel.selectedAor.collectAsState()
|
||||||
val filteredCalls = calls.filter { it.ua.account.aor == selectedAor && it.status.value != "disconnecting" }
|
val aorCalls = calls.filter { it.ua.account.aor == selectedAor }
|
||||||
val dialingOrRinging = filteredCalls.any { it.status.value == "outgoing" || it.status.value == "incoming" }
|
val hasActiveCalls = aorCalls.any { !it.callOnHold.value }
|
||||||
val conferenceCall = filteredCalls.any { it.conferenceCall }
|
val conferenceCall = aorCalls.any { it.conferenceCall }
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
LaunchedEffect(isRefreshing) {
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
@@ -997,12 +999,13 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
) {
|
) {
|
||||||
AccountSpinner(ctx, viewModel, navController)
|
AccountSpinner(ctx, viewModel, navController)
|
||||||
|
|
||||||
filteredCalls.forEach { call ->
|
aorCalls.forEach { call ->
|
||||||
CallCard(ctx = ctx, viewModel = viewModel, call = call, dialerState = null)
|
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 (!hasActiveCalls || conferenceCall)
|
||||||
if (!dialingOrRinging && (filteredCalls.isEmpty() || conferenceCall))
|
|
||||||
CallCard(ctx = ctx, viewModel = viewModel, call = null, dialerState = viewModel.dialerState)
|
CallCard(ctx = ctx, viewModel = viewModel, call = null, dialerState = viewModel.dialerState)
|
||||||
|
|
||||||
Indicator(
|
Indicator(
|
||||||
@@ -1539,7 +1542,7 @@ private fun CallRow(
|
|||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
val connection = ConnectionService.connections[call.callp]
|
val connection = ConnectionService.connections[call.callp]
|
||||||
if (call.onhold) {
|
if (call.callOnHold.value) {
|
||||||
if (!Call.isAnyCallActive(ctx)) {
|
if (!Call.isAnyCallActive(ctx)) {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -1549,6 +1552,7 @@ private fun CallRow(
|
|||||||
connection.onUnhold()
|
connection.onUnhold()
|
||||||
else
|
else
|
||||||
call.resume()
|
call.resume()
|
||||||
|
call.callOnHold.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1560,6 +1564,7 @@ private fun CallRow(
|
|||||||
connection.onHold()
|
connection.onHold()
|
||||||
else
|
else
|
||||||
call.hold()
|
call.hold()
|
||||||
|
call.callOnHold.value = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -1728,8 +1733,10 @@ private fun CallRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
transferUri = suggestion.toString()
|
transferUri =
|
||||||
call.showSuggestions.value = false
|
suggestion.toString()
|
||||||
|
call.showSuggestions.value =
|
||||||
|
false
|
||||||
}
|
}
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user