Added conference calling
This commit is contained in:
@@ -11,6 +11,7 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
|||||||
|
|
||||||
var onhold = false
|
var onhold = false
|
||||||
var held = false
|
var held = false
|
||||||
|
var conferenceCall = false
|
||||||
var onHoldCall: Call? = null
|
var onHoldCall: Call? = null
|
||||||
var newCall: Call? = null
|
var newCall: Call? = null
|
||||||
var rejected = false // Incoming rejected by user or outgoing fails but not due to 408 or 480
|
var rejected = false // Incoming rejected by user or outgoing fails but not due to 408 or 480
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ import androidx.compose.material.icons.filled.RecordVoiceOver
|
|||||||
import androidx.compose.material.icons.filled.SpeakerPhone
|
import androidx.compose.material.icons.filled.SpeakerPhone
|
||||||
import androidx.compose.material.icons.filled.VoiceOverOff
|
import androidx.compose.material.icons.filled.VoiceOverOff
|
||||||
import androidx.compose.material.icons.filled.Voicemail
|
import androidx.compose.material.icons.filled.Voicemail
|
||||||
|
import androidx.compose.material.icons.filled.WifiCalling
|
||||||
import androidx.compose.material.icons.outlined.ArrowCircleRight
|
import androidx.compose.material.icons.outlined.ArrowCircleRight
|
||||||
import androidx.compose.material.icons.outlined.Clear
|
import androidx.compose.material.icons.outlined.Clear
|
||||||
import androidx.compose.material.icons.outlined.Info
|
import androidx.compose.material.icons.outlined.Info
|
||||||
@@ -882,6 +883,7 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
val filteredCalls = calls.filter { it.ua.account.aor == selectedAor }
|
val filteredCalls = calls.filter { it.ua.account.aor == selectedAor }
|
||||||
|
|
||||||
val dialingOrRinging = filteredCalls.any { it.status.value == "outgoing" || it.status.value == "incoming" }
|
val dialingOrRinging = filteredCalls.any { it.status.value == "outgoing" || it.status.value == "incoming" }
|
||||||
|
val conferenceCall = filteredCalls.any { it.conferenceCall }
|
||||||
|
|
||||||
LaunchedEffect(isRefreshing) {
|
LaunchedEffect(isRefreshing) {
|
||||||
if (isRefreshing) {
|
if (isRefreshing) {
|
||||||
@@ -987,9 +989,8 @@ private fun MainContent(navController: NavController, viewModel: ViewModel, cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only show the dialer if we are not in a transient state
|
// Only show the dialer if we are not in a transient state
|
||||||
if (!dialingOrRinging) {
|
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(
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
@@ -1406,23 +1407,50 @@ private fun CallRow(
|
|||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
|
dialerState.showCallConferenceButton.value = false
|
||||||
dialerState.showSuggestions.value = false
|
dialerState.showSuggestions.value = false
|
||||||
callClick(ctx, viewModel, dialerState)
|
callClick(ctx, viewModel, dialerState)
|
||||||
},
|
},
|
||||||
enabled = dialerState.callButtonEnabled.value
|
enabled = dialerState.callButtonsEnabled.value
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Call,
|
imageVector = Icons.Filled.Call,
|
||||||
modifier = Modifier.size(42.dp),
|
modifier = Modifier.size(42.dp),
|
||||||
tint = colorResource(if (dialerState.callButtonEnabled.value)
|
tint = colorResource(if (dialerState.callButtonsEnabled.value)
|
||||||
R.color.colorTrafficGreen
|
R.color.colorTrafficGreen
|
||||||
else
|
else
|
||||||
R.color.colorTrafficYellow),
|
R.color.colorTrafficYellow),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
if (dialerState.showCallConferenceButton.value) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f, true))
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(48.dp),
|
||||||
|
enabled = dialerState.callButtonsEnabled.value,
|
||||||
|
onClick = {
|
||||||
|
dialerState.showCallButton.value = false
|
||||||
|
dialerState.showSuggestions.value = false
|
||||||
|
callClick(ctx, viewModel, dialerState)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.WifiCalling,
|
||||||
|
modifier = Modifier.size(42.dp),
|
||||||
|
tint = colorResource(
|
||||||
|
if (dialerState.callButtonsEnabled.value)
|
||||||
|
R.color.colorTrafficGreen
|
||||||
|
else
|
||||||
|
R.color.colorTrafficYellow
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
if (call!!.showCancelButton.value) {
|
if (call!!.showCancelButton.value) {
|
||||||
|
if (!call.conferenceCall)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
@@ -1495,6 +1523,8 @@ private fun CallRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var showTransferDialog by remember { mutableStateOf(false) }
|
var showTransferDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (!call.conferenceCall)
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
enabled = call.transferButtonEnabled.value,
|
enabled = call.transferButtonEnabled.value,
|
||||||
@@ -1747,6 +1777,7 @@ private fun CallRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
@@ -1919,13 +1950,13 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
|
|||||||
if (uriText.isNotEmpty()) {
|
if (uriText.isNotEmpty()) {
|
||||||
val uris = Contact.contactUris(uriText)
|
val uris = Contact.contactUris(uriText)
|
||||||
if (uris.isEmpty())
|
if (uris.isEmpty())
|
||||||
makeCall(ctx, viewModel, uriText)
|
makeCall(ctx, viewModel, uriText, dialerState.showCallConferenceButton.value)
|
||||||
else if (uris.size == 1)
|
else if (uris.size == 1)
|
||||||
makeCall(ctx, viewModel, uris[0])
|
makeCall(ctx, viewModel, uris[0], dialerState.showCallConferenceButton.value)
|
||||||
else {
|
else {
|
||||||
selectItems.value = uris
|
selectItems.value = uris
|
||||||
selectItemAction.value = { index ->
|
selectItemAction.value = { index ->
|
||||||
makeCall(ctx, viewModel, uris[index])
|
makeCall(ctx, viewModel, uris[index], dialerState.showCallConferenceButton.value)
|
||||||
}
|
}
|
||||||
showSelectItemDialog.value = true
|
showSelectItemDialog.value = true
|
||||||
}
|
}
|
||||||
@@ -1942,7 +1973,7 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String) {
|
private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, conferenceCall: Boolean) {
|
||||||
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!
|
||||||
val aor = ua.account.aor
|
val aor = ua.account.aor
|
||||||
@@ -1969,14 +2000,14 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String) {
|
|||||||
else if (!BaresipService.requestAudioFocus(ctx))
|
else if (!BaresipService.requestAudioFocus(ctx))
|
||||||
Toast.makeText(ctx, R.string.audio_focus_denied, Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, R.string.audio_focus_denied, Toast.LENGTH_SHORT).show()
|
||||||
else {
|
else {
|
||||||
viewModel.dialerState.callButtonEnabled.value = false
|
viewModel.dialerState.callButtonsEnabled.value = false
|
||||||
if (Build.VERSION.SDK_INT < 31) {
|
if (Build.VERSION.SDK_INT < 31) {
|
||||||
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
|
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
|
||||||
am.mode = AudioManager.MODE_IN_COMMUNICATION
|
am.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||||
runCall(ctx, viewModel, ua, uri)
|
runCall(ctx, viewModel, ua, uri, conferenceCall)
|
||||||
} else {
|
} else {
|
||||||
if (am.mode == AudioManager.MODE_IN_COMMUNICATION) {
|
if (am.mode == AudioManager.MODE_IN_COMMUNICATION) {
|
||||||
runCall(ctx, viewModel, ua, uri)
|
runCall(ctx, viewModel, ua, uri, conferenceCall)
|
||||||
} else {
|
} else {
|
||||||
audioModeChangedListener = AudioManager.OnModeChangedListener { mode ->
|
audioModeChangedListener = AudioManager.OnModeChangedListener { mode ->
|
||||||
if (mode == AudioManager.MODE_IN_COMMUNICATION) {
|
if (mode == AudioManager.MODE_IN_COMMUNICATION) {
|
||||||
@@ -1986,7 +2017,7 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String) {
|
|||||||
am.removeOnModeChangedListener(audioModeChangedListener!!)
|
am.removeOnModeChangedListener(audioModeChangedListener!!)
|
||||||
audioModeChangedListener = null
|
audioModeChangedListener = null
|
||||||
}
|
}
|
||||||
runCall(ctx, viewModel, ua, uri)
|
runCall(ctx, viewModel, ua, uri, conferenceCall)
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Audio mode changed to mode ${am.mode} using " +
|
Log.d(TAG, "Audio mode changed to mode ${am.mode} using " +
|
||||||
"device ${am.communicationDevice!!.type}")
|
"device ${am.communicationDevice!!.type}")
|
||||||
@@ -2015,13 +2046,13 @@ private fun reject(call: Call) {
|
|||||||
Api.ua_hangup(call.ua.uap, call.callp, 486, "Busy Here")
|
Api.ua_hangup(call.ua.uap, call.callp, 486, "Busy Here")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runCall(ctx: Context, viewModel: ViewModel, ua: UserAgent, uri: String) {
|
private fun runCall(ctx: Context, viewModel: ViewModel, ua: UserAgent, uri: String, conferenceCall: Boolean) {
|
||||||
callRunnable = Runnable {
|
callRunnable = Runnable {
|
||||||
callRunnable = null
|
callRunnable = null
|
||||||
val newCall = call(ctx, viewModel, ua, uri)
|
val newCall = call(ctx, viewModel, ua, uri, conferenceCall)
|
||||||
if (newCall == null) {
|
if (newCall == null) {
|
||||||
BaresipService.abandonAudioFocus(ctx)
|
BaresipService.abandonAudioFocus(ctx)
|
||||||
viewModel.dialerState.callButtonEnabled.value = true
|
viewModel.dialerState.callButtonsEnabled.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callHandler.postDelayed(callRunnable!!, BaresipService.audioDelay)
|
callHandler.postDelayed(callRunnable!!, BaresipService.audioDelay)
|
||||||
@@ -2032,6 +2063,7 @@ private fun call(
|
|||||||
viewModel: ViewModel,
|
viewModel: ViewModel,
|
||||||
ua: UserAgent,
|
ua: UserAgent,
|
||||||
uri: String,
|
uri: String,
|
||||||
|
conferenceCall: Boolean,
|
||||||
onHoldCall: Call? = null
|
onHoldCall: Call? = null
|
||||||
): Call? {
|
): Call? {
|
||||||
spinToAor(viewModel, ua.account.aor)
|
spinToAor(viewModel, ua.account.aor)
|
||||||
@@ -2040,6 +2072,7 @@ private fun call(
|
|||||||
Log.d(TAG, "Adding outgoing call ${ua.uap}/$callp/$uri")
|
Log.d(TAG, "Adding outgoing call ${ua.uap}/$callp/$uri")
|
||||||
val call = Call(callp, ua, uri, "out", "outgoing")
|
val call = Call(callp, ua, uri, "out", "outgoing")
|
||||||
call.onHoldCall = onHoldCall
|
call.onHoldCall = onHoldCall
|
||||||
|
call.conferenceCall = conferenceCall
|
||||||
call.add()
|
call.add()
|
||||||
if (onHoldCall != null)
|
if (onHoldCall != null)
|
||||||
onHoldCall.newCall = call
|
onHoldCall.newCall = call
|
||||||
@@ -2077,7 +2110,7 @@ private fun transfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, uriText:
|
|||||||
if (attended) {
|
if (attended) {
|
||||||
if (call.hold()) {
|
if (call.hold()) {
|
||||||
call.referTo = uri
|
call.referTo = uri
|
||||||
call(ctx, viewModel, ua, uri, call)
|
call(ctx, viewModel, ua, uri, false,call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -2102,7 +2135,8 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
|
|||||||
viewModel.dialerState.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
|
viewModel.dialerState.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
|
||||||
viewModel.dialerState.callUriEnabled.value = true
|
viewModel.dialerState.callUriEnabled.value = true
|
||||||
viewModel.dialerState.showCallButton.value = true
|
viewModel.dialerState.showCallButton.value = true
|
||||||
viewModel.dialerState.callButtonEnabled.value = true
|
viewModel.dialerState.showCallConferenceButton.value = true
|
||||||
|
viewModel.dialerState.callButtonsEnabled.value = true
|
||||||
viewModel.dialerState.showSuggestions.value = false
|
viewModel.dialerState.showSuggestions.value = false
|
||||||
dialpadButtonEnabled.value = true
|
dialpadButtonEnabled.value = true
|
||||||
if (BaresipService.isMicMuted) {
|
if (BaresipService.isMicMuted) {
|
||||||
@@ -2279,7 +2313,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
}
|
}
|
||||||
"call established" -> {
|
"call established" -> {
|
||||||
if (aor == viewModel.selectedAor.value) {
|
if (aor == viewModel.selectedAor.value) {
|
||||||
viewModel.dialerState.callButtonEnabled.value = true // Re-enable dialer
|
viewModel.dialerState.callButtonsEnabled.value = true // Re-enable dialer
|
||||||
val callp = params[1] as Long
|
val callp = params[1] as Long
|
||||||
val call = Call.ofCallp(callp)
|
val call = Call.ofCallp(callp)
|
||||||
if (call != null) {
|
if (call != null) {
|
||||||
@@ -2351,7 +2385,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
if (call in Call.calls())
|
if (call in Call.calls())
|
||||||
acceptTransfer(ctx, viewModel, ua, call!!, ev[1])
|
acceptTransfer(ctx, viewModel, ua, call!!, ev[1])
|
||||||
else
|
else
|
||||||
makeCall(ctx, viewModel, ev[1])
|
makeCall(ctx, viewModel, ev[1], false)
|
||||||
}
|
}
|
||||||
negativeText.value = ctx.getString(R.string.no)
|
negativeText.value = ctx.getString(R.string.no)
|
||||||
onNegativeClicked.value = {
|
onNegativeClicked.value = {
|
||||||
@@ -2366,7 +2400,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
val call = Call.ofCallp(callp)
|
val call = Call.ofCallp(callp)
|
||||||
if (call in Call.calls())
|
if (call in Call.calls())
|
||||||
Api.ua_hangup(uap, callp, 487, "Request Terminated")
|
Api.ua_hangup(uap, callp, 487, "Request Terminated")
|
||||||
call(ctx, viewModel, ua, ev[1])
|
call(ctx, viewModel, ua, ev[1], false)
|
||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
}
|
}
|
||||||
"transfer failed" -> {
|
"transfer failed" -> {
|
||||||
@@ -2374,7 +2408,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
|||||||
}
|
}
|
||||||
"call closed" -> {
|
"call closed" -> {
|
||||||
if (aor == viewModel.selectedAor.value) {
|
if (aor == viewModel.selectedAor.value) {
|
||||||
viewModel.dialerState.callButtonEnabled.value = true
|
viewModel.dialerState.callButtonsEnabled.value = true
|
||||||
ua.account.resumeUri = ""
|
ua.account.resumeUri = ""
|
||||||
showCall(ctx, viewModel, ua)
|
showCall(ctx, viewModel, ua)
|
||||||
if (acc.missedCalls)
|
if (acc.missedCalls)
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ class ViewModel: ViewModel() {
|
|||||||
val callUriLabel: MutableState<String> = mutableStateOf(""),
|
val callUriLabel: MutableState<String> = mutableStateOf(""),
|
||||||
val showSuggestions: MutableState<Boolean> = mutableStateOf(false),
|
val showSuggestions: MutableState<Boolean> = mutableStateOf(false),
|
||||||
val showCallButton: MutableState<Boolean> = mutableStateOf(true),
|
val showCallButton: MutableState<Boolean> = mutableStateOf(true),
|
||||||
val callButtonEnabled: MutableState<Boolean> = mutableStateOf(true),
|
val showCallConferenceButton: MutableState<Boolean> = mutableStateOf(true),
|
||||||
|
val callButtonsEnabled: MutableState<Boolean> = mutableStateOf(true),
|
||||||
|
val conferenceCall: MutableState<Boolean> = mutableStateOf(false),
|
||||||
)
|
)
|
||||||
|
|
||||||
val dialerState = DialerState()
|
val dialerState = DialerState()
|
||||||
|
|||||||
Reference in New Issue
Block a user