Started experimenting with baresip as PSTN dialer
This commit is contained in:
@ -1748,6 +1748,77 @@ class BaresipService: Service() {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleExternalCall(telecomCall: android.telecom.Call) {
|
||||
val uri = telecomCall.details.handle?.schemeSpecificPart ?: "Unknown"
|
||||
Log.d(TAG, "Handling external call from $uri")
|
||||
|
||||
if (uas.value.isEmpty()) {
|
||||
Log.e(TAG, "No User Agents available to handle external call")
|
||||
return
|
||||
}
|
||||
val ua = UserAgent.statusMap().keys.firstOrNull()?.let { UserAgent.ofAor(it) } ?: uas.value[0]
|
||||
|
||||
val telecomState = if (VERSION.SDK_INT >= 31)
|
||||
telecomCall.details.state
|
||||
else
|
||||
@Suppress("DEPRECATION") telecomCall.state
|
||||
val initialStatus = when (telecomState) {
|
||||
android.telecom.Call.STATE_RINGING -> "incoming"
|
||||
android.telecom.Call.STATE_DIALING, android.telecom.Call.STATE_CONNECTING -> "outgoing"
|
||||
else -> "connected"
|
||||
}
|
||||
|
||||
val call = Call.ExternalCall(
|
||||
telecomCall,
|
||||
ua,
|
||||
uri,
|
||||
if (telecomState == android.telecom.Call.STATE_RINGING) "in" else "out",
|
||||
initialStatus
|
||||
)
|
||||
|
||||
telecomCall.registerCallback(object : android.telecom.Call.Callback() {
|
||||
override fun onStateChanged(call: android.telecom.Call, state: Int) {
|
||||
super.onStateChanged(call, state)
|
||||
val newStatus = when (state) {
|
||||
android.telecom.Call.STATE_RINGING -> "incoming"
|
||||
android.telecom.Call.STATE_DIALING, android.telecom.Call.STATE_CONNECTING -> "outgoing"
|
||||
android.telecom.Call.STATE_ACTIVE -> "connected"
|
||||
android.telecom.Call.STATE_DISCONNECTED -> "closed"
|
||||
android.telecom.Call.STATE_HOLDING -> {
|
||||
calls.find { it.callp == call.hashCode().toLong() }?.onhold = true
|
||||
"connected"
|
||||
}
|
||||
else -> "connected"
|
||||
}
|
||||
calls.find { it.callp == call.hashCode().toLong() }?.let {
|
||||
if (it.status.value != newStatus) {
|
||||
it.status.value = newStatus
|
||||
postServiceEvent(ServiceEvent(
|
||||
"call update",
|
||||
arrayListOf(it.ua.uap, it.callp),
|
||||
System.nanoTime())
|
||||
)
|
||||
if (newStatus == "closed")
|
||||
handleExternalCallRemoved(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
calls.add(call)
|
||||
postServiceEvent(ServiceEvent(
|
||||
"call incoming",
|
||||
arrayListOf(ua.uap, call.callp),
|
||||
System.nanoTime())
|
||||
)
|
||||
}
|
||||
|
||||
fun handleExternalCallRemoved(telecomCall: android.telecom.Call) {
|
||||
val callp = telecomCall.hashCode().toLong()
|
||||
calls.removeAll { it.callp == callp }
|
||||
messageUpdate.postValue(System.currentTimeMillis())
|
||||
}
|
||||
|
||||
private fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(this@BaresipService.applicationContext, message, length).show()
|
||||
|
||||
@ -8,7 +8,7 @@ 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) {
|
||||
open class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: String, initialStatus: String) {
|
||||
|
||||
var status: MutableState<String> = mutableStateOf(initialStatus)
|
||||
|
||||
@ -55,11 +55,11 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
||||
BaresipService.calls.remove(this)
|
||||
}
|
||||
|
||||
fun connect(uri: String): Boolean {
|
||||
open fun connect(uri: String): Boolean {
|
||||
return Api.call_connect(callp, uri) == 0
|
||||
}
|
||||
|
||||
fun hold(): Boolean {
|
||||
open fun hold(): Boolean {
|
||||
if (onhold) return true
|
||||
if (Api.call_hold(callp, true)) {
|
||||
onhold = true
|
||||
@ -71,7 +71,7 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
||||
return false
|
||||
}
|
||||
|
||||
fun resume(): Boolean {
|
||||
open fun resume(): Boolean {
|
||||
if (!onhold && !held) return true
|
||||
// 1. Hold other calls first
|
||||
for (c in BaresipService.calls) {
|
||||
@ -94,13 +94,13 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
||||
return false
|
||||
}
|
||||
|
||||
fun transfer(uri: String): Boolean {
|
||||
open fun transfer(uri: String): Boolean {
|
||||
if (!onhold) hold()
|
||||
Log.d(TAG, "Transferring call $callp to $uri")
|
||||
return Api.call_transfer(callp, uri) == 0
|
||||
}
|
||||
|
||||
fun executeTransfer(): Boolean {
|
||||
open fun executeTransfer(): Boolean {
|
||||
return if (onHoldCall != null) {
|
||||
if (Api.call_hold(callp, true))
|
||||
Api.call_replace_transfer(onHoldCall!!.callp, callp)
|
||||
@ -110,31 +110,31 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
||||
false
|
||||
}
|
||||
|
||||
fun sendDigit(digit: Char): Int {
|
||||
open fun sendDigit(digit: Char): Int {
|
||||
return Api.call_send_digit(callp, digit)
|
||||
}
|
||||
|
||||
fun notifySipfrag(code: Int, reason: String) {
|
||||
open fun notifySipfrag(code: Int, reason: String) {
|
||||
Api.call_notify_sipfrag(callp, code, reason)
|
||||
}
|
||||
|
||||
fun duration(): Int {
|
||||
open fun duration(): Int {
|
||||
return Api.call_duration(callp)
|
||||
}
|
||||
|
||||
fun stats(stream: String): String {
|
||||
open fun stats(stream: String): String {
|
||||
return Api.call_stats(callp, stream)
|
||||
}
|
||||
|
||||
fun state(): Int {
|
||||
open fun state(): Int {
|
||||
return Api.call_state(callp)
|
||||
}
|
||||
|
||||
fun audioCodecs(): String {
|
||||
open fun audioCodecs(): String {
|
||||
return Api.call_audio_codecs(callp)
|
||||
}
|
||||
|
||||
fun replaces(): Boolean {
|
||||
open fun replaces(): Boolean {
|
||||
return Api.call_replaces(callp)
|
||||
}
|
||||
|
||||
@ -146,10 +146,53 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
||||
if (ua.account.mediaEnc != "") security = R.color.colorTrafficRed
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
open fun destroy() {
|
||||
Api.call_destroy(callp)
|
||||
}
|
||||
|
||||
class ExternalCall(
|
||||
val telecomCall: android.telecom.Call,
|
||||
ua: UserAgent,
|
||||
peerUri: String,
|
||||
dir: String,
|
||||
initialStatus: String
|
||||
) : Call(telecomCall.hashCode().toLong(), ua, peerUri, dir, initialStatus) {
|
||||
|
||||
override fun connect(uri: String): Boolean {
|
||||
telecomCall.answer(android.telecom.VideoProfile.STATE_AUDIO_ONLY)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hold(): Boolean {
|
||||
telecomCall.hold()
|
||||
onhold = true
|
||||
callOnHold.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun resume(): Boolean {
|
||||
telecomCall.unhold()
|
||||
onhold = false
|
||||
callOnHold.value = false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
telecomCall.disconnect()
|
||||
}
|
||||
|
||||
override fun sendDigit(digit: Char): Int {
|
||||
telecomCall.playDtmfTone(digit)
|
||||
telecomCall.stopDtmfTone()
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun duration(): Int = 0
|
||||
override fun stats(stream: String): String = ""
|
||||
override fun state(): Int = 0
|
||||
override fun audioCodecs(): String = "PSTN"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun calls(): ArrayList<Call> {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.telecom.CallAudioState
|
||||
import android.telecom.Connection
|
||||
import android.telecom.ConnectionRequest
|
||||
@ -8,7 +9,6 @@ import android.telecom.ConnectionService
|
||||
import android.telecom.DisconnectCause
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.telecom.TelecomManager
|
||||
import android.net.Uri
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class ConnectionService : ConnectionService() {
|
||||
@ -64,7 +64,8 @@ class ConnectionService : ConnectionService() {
|
||||
|
||||
val connection = BaresipConnection(uap, callp)
|
||||
connections[callp] = connection
|
||||
connection.setAddress(Uri.fromParts("sip", peerUri, null), TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.setAddress(Uri.fromParts("sip", peerUri, null),
|
||||
TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or
|
||||
Connection.CAPABILITY_HOLD or
|
||||
Connection.CAPABILITY_MERGE_CONFERENCE or
|
||||
@ -108,6 +109,9 @@ class ConnectionService : ConnectionService() {
|
||||
val conferenceCall = rootExtras?.getBoolean("conferenceCall", false) ?:
|
||||
nestedExtras?.getBoolean("conferenceCall") ?: false
|
||||
|
||||
val pstnCall = rootExtras?.getBoolean("pstnCall", false) ?:
|
||||
nestedExtras?.getBoolean("pstnCall") ?: false
|
||||
|
||||
val onHoldCallp = rootExtras?.getLong("onHoldCallp", 0L).takeIf { it != 0L }
|
||||
?: nestedExtras?.getLong("onHoldCallp") ?: 0L
|
||||
|
||||
@ -123,20 +127,15 @@ class ConnectionService : ConnectionService() {
|
||||
Connection.CAPABILITY_HOLD or
|
||||
Connection.CAPABILITY_MERGE_CONFERENCE or
|
||||
Connection.CAPABILITY_SWAP_CONFERENCE
|
||||
connection.audioModeIsVoip = true
|
||||
|
||||
// Start the SIP connection logic
|
||||
if (uap != 0L) {
|
||||
if (!pstnCall) {
|
||||
connection.audioModeIsVoip = true
|
||||
val sipUri = if (destination.startsWith("sip:")) destination else "sip:$destination"
|
||||
BaresipService.instance?.runCall(uap, sipUri, conferenceCall, onHoldCallp)
|
||||
} else {
|
||||
Log.e(TAG, "Cannot start outgoing call: uap is 0")
|
||||
connection.setDisconnected(DisconnectCause(DisconnectCause.ERROR, "No Account"))
|
||||
connection.destroy()
|
||||
pendingOutgoingConnection = null
|
||||
}
|
||||
|
||||
connection.setDialing()
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
|
||||
@ -1,24 +1,35 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
|
||||
// This is needed in order to allow choosing baresip as default Phone app
|
||||
class InCallService : Service() {
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
Log.d(TAG, "InCallService onBind with intent: ${intent.action}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/*import android.telecom.InCallService
|
||||
import android.telecom.Call
|
||||
import android.telecom.InCallService
|
||||
|
||||
class InCallService : InCallService() {
|
||||
|
||||
override fun onCallAdded(call: Call) {
|
||||
super.onCallAdded(call)
|
||||
// This is triggered when the system wants YOU to show the call UI
|
||||
Log.d("Baresip", "InCallService: Call added")
|
||||
Log.d(TAG, "InCallService: Call added")
|
||||
|
||||
val handle = call.details.accountHandle
|
||||
val baresipHandle = BaresipService.getPhoneAccountHandle(this)
|
||||
|
||||
if (handle == baresipHandle) {
|
||||
Log.d(TAG, "InCallService: Identified as SIP call")
|
||||
// The SIP call is already managed by ConnectionService/BaresipService.
|
||||
// We just need to ensure the InCallService stays bound.
|
||||
} else {
|
||||
Log.d(TAG, "InCallService: Identified as PSTN call from $handle")
|
||||
// This is a cellular call. We need to wrap it so MainScreen can show it.
|
||||
BaresipService.instance?.handleExternalCall(call)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
override fun onCallRemoved(call: Call) {
|
||||
super.onCallRemoved(call)
|
||||
Log.d(TAG, "InCallService: Call removed")
|
||||
BaresipService.instance?.handleExternalCallRemoved(call)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Baresip"
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,12 +12,14 @@ import android.content.res.Configuration
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Process
|
||||
import android.os.SystemClock
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import android.telecom.TelecomManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@ -1473,12 +1475,14 @@ private fun CallRow(
|
||||
horizontalArrangement = Arrangement.Absolute.SpaceBetween
|
||||
) {
|
||||
if (isDialer) {
|
||||
dialerState.showCallPstnButton.value = Utils.pstnAccountHandle(ctx) != null
|
||||
if (dialerState.showCallButton.value)
|
||||
IconButton(
|
||||
modifier = Modifier.size(48.dp),
|
||||
enabled = dialerState.callButtonsEnabled.value,
|
||||
onClick = {
|
||||
if (!dialerState.callButtonsEnabled.value) return@IconButton
|
||||
dialerState.showCallPstnButton.value = false
|
||||
dialerState.showCallConferenceButton.value = false
|
||||
dialerState.showSuggestions.value = false
|
||||
callClick(ctx, viewModel, dialerState)
|
||||
@ -1494,6 +1498,28 @@ private fun CallRow(
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
if (dialerState.showCallPstnButton.value)
|
||||
IconButton(
|
||||
modifier = Modifier.size(48.dp),
|
||||
enabled = dialerState.callButtonsEnabled.value,
|
||||
onClick = {
|
||||
if (!dialerState.callButtonsEnabled.value) return@IconButton
|
||||
dialerState.showCallButton.value = false
|
||||
dialerState.showCallConferenceButton.value = false
|
||||
dialerState.showSuggestions.value = false
|
||||
callClick(ctx, viewModel, dialerState)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.call_tel),
|
||||
modifier = Modifier.size(42.dp),
|
||||
tint = colorResource(if (dialerState.callButtonsEnabled.value)
|
||||
R.color.colorTrafficGreen
|
||||
else
|
||||
R.color.colorTrafficYellow),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
if (dialerState.showCallConferenceButton.value) {
|
||||
Spacer(modifier = Modifier.weight(1f, true))
|
||||
IconButton(
|
||||
@ -1502,6 +1528,7 @@ private fun CallRow(
|
||||
onClick = {
|
||||
if (!dialerState.callButtonsEnabled.value) return@IconButton
|
||||
dialerState.showCallButton.value = false
|
||||
dialerState.showCallPstnButton.value = false
|
||||
dialerState.showSuggestions.value = false
|
||||
callClick(ctx, viewModel, dialerState)
|
||||
}
|
||||
@ -2061,14 +2088,14 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
|
||||
ctx,
|
||||
viewModel,
|
||||
uriText,
|
||||
dialerState.showCallConferenceButton.value
|
||||
dialerState
|
||||
)
|
||||
else if (uris.size == 1)
|
||||
makeCall(
|
||||
ctx,
|
||||
viewModel,
|
||||
uris[0],
|
||||
dialerState.showCallConferenceButton.value
|
||||
dialerState
|
||||
)
|
||||
else {
|
||||
selectItems.value = uris
|
||||
@ -2077,7 +2104,7 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
|
||||
ctx,
|
||||
viewModel,
|
||||
uris[index],
|
||||
dialerState.showCallConferenceButton.value
|
||||
dialerState
|
||||
)
|
||||
}
|
||||
showSelectItemDialog.value = true
|
||||
@ -2095,8 +2122,8 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, conferenceCall: Boolean,
|
||||
onHoldCallp: Long = 0L) {
|
||||
private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
|
||||
dialerState: ViewModel.DialerState, onHoldCallp: Long = 0L) {
|
||||
val aor = viewModel.selectedAor.value
|
||||
val ua = UserAgent.ofAor(aor)!!
|
||||
val peerUri = if (Utils.isTelNumber(uriText))
|
||||
@ -2104,13 +2131,16 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, confer
|
||||
else
|
||||
uriText
|
||||
val uri = if (Utils.isTelUri(peerUri)) {
|
||||
if (ua.account.telProvider == "") {
|
||||
if (dialerState.showCallPstnButton.value)
|
||||
peerUri
|
||||
else if (ua.account.telProvider == "") {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = String.format(ctx.getString(R.string.no_telephony_provider), aor)
|
||||
showAlert.value = true
|
||||
return
|
||||
}
|
||||
Utils.telToSip(peerUri, ua.account)
|
||||
else
|
||||
Utils.telToSip(peerUri, ua.account)
|
||||
}
|
||||
else
|
||||
Utils.uriComplete(peerUri, aor)
|
||||
@ -2118,28 +2148,62 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, confer
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), uri)
|
||||
showAlert.value = true
|
||||
return
|
||||
}
|
||||
else if (dialerState.showCallPstnButton.value && !Utils.isTelUri(uri)) {
|
||||
alertTitle.value = ctx.getString(R.string.notice)
|
||||
alertMessage.value = "Telephone call can only be made to telephone number"
|
||||
showAlert.value = true
|
||||
return
|
||||
}
|
||||
else if (Utils.isAudioMode(ctx,AudioManager.MODE_IN_CALL) &&
|
||||
!Call.calls().any { it.ua.account.aor == ua.account.aor })
|
||||
Toast.makeText(ctx, R.string.call_already_active, Toast.LENGTH_SHORT).show()
|
||||
else {
|
||||
viewModel.dialerState.callButtonsEnabled.value = false
|
||||
var error = ""
|
||||
if (BaresipService.telecom) {
|
||||
val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as android.telecom.TelecomManager
|
||||
val extras = android.os.Bundle()
|
||||
extras.putParcelable(android.telecom.TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||
BaresipService.getPhoneAccountHandle(ctx))
|
||||
val callExtras = android.os.Bundle()
|
||||
callExtras.putBoolean("conferenceCall", conferenceCall)
|
||||
callExtras.putLong("uap", ua.uap)
|
||||
if (onHoldCallp != 0L)
|
||||
callExtras.putLong("onHoldCallp", onHoldCallp)
|
||||
extras.putBundle(android.telecom.TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
|
||||
try {
|
||||
Log.d(TAG, "Placing Telecom call to $uri with uap=${ua.uap}")
|
||||
tm.placeCall(uri.toUri(), extras)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "placeCall failed: ${e.message}")
|
||||
val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
||||
if (dialerState.showCallPstnButton.value) {
|
||||
val phoneAccountHandle = Utils.pstnAccountHandle(ctx)
|
||||
if (phoneAccountHandle != null) {
|
||||
val extras = Bundle().apply {
|
||||
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
|
||||
}
|
||||
val callExtras = Bundle()
|
||||
callExtras.putBoolean("pstnCall", dialerState.showCallPstnButton.value)
|
||||
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
|
||||
try {
|
||||
Log.i(TAG, "Placing Telecom PSTN call to $uri with uap=${ua.uap}")
|
||||
tm.placeCall(uri.toUri(), extras)
|
||||
} catch (e: SecurityException) {
|
||||
error = "placeCall failed: ${e.message}"
|
||||
}
|
||||
}
|
||||
else
|
||||
error = "no phone account"
|
||||
}
|
||||
else {
|
||||
val extras = Bundle()
|
||||
extras.putParcelable(
|
||||
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
|
||||
BaresipService.getPhoneAccountHandle(ctx)
|
||||
)
|
||||
val callExtras = Bundle()
|
||||
callExtras.putBoolean("conferenceCall", dialerState.showCallConferenceButton.value)
|
||||
callExtras.putLong("uap", ua.uap)
|
||||
if (onHoldCallp != 0L)
|
||||
callExtras.putLong("onHoldCallp", onHoldCallp)
|
||||
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
|
||||
try {
|
||||
Log.d(TAG, "Placing Telecom SIP call to $uri with uap=${ua.uap}")
|
||||
tm.placeCall(uri.toUri(), extras)
|
||||
} catch (e: SecurityException) {
|
||||
error = "placeCall failed: ${e.message}"
|
||||
}
|
||||
}
|
||||
if (error != "") {
|
||||
Log.e(TAG, error)
|
||||
viewModel.dialerState.callButtonsEnabled.value = true
|
||||
}
|
||||
}
|
||||
@ -2148,7 +2212,7 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, confer
|
||||
intent.action = "Start Call"
|
||||
intent.putExtra("uap", ua.uap)
|
||||
intent.putExtra("uri", uri)
|
||||
intent.putExtra("conferenceCall", conferenceCall)
|
||||
intent.putExtra("conferenceCall", dialerState.showCallConferenceButton.value)
|
||||
intent.putExtra("onHoldCallp", onHoldCallp)
|
||||
ctx.startService(intent)
|
||||
}
|
||||
@ -2207,7 +2271,7 @@ private fun transfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, uriText:
|
||||
if (success) {
|
||||
call.onhold = true
|
||||
call.referTo = uri
|
||||
makeCall(ctx, viewModel, uri, false, call.callp)
|
||||
makeCall(ctx, viewModel, uri, viewModel.dialerState, call.callp)
|
||||
showCall(ctx, viewModel, ua, call)
|
||||
}
|
||||
}
|
||||
@ -2237,6 +2301,7 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
|
||||
viewModel.dialerState.callUriEnabled.value = true
|
||||
}, 100)
|
||||
viewModel.dialerState.showCallButton.value = true
|
||||
viewModel.dialerState.showCallPstnButton.value = true
|
||||
viewModel.dialerState.showCallConferenceButton.value = true
|
||||
viewModel.dialerState.callButtonsEnabled.value = true
|
||||
viewModel.dialerState.showSuggestions.value = false
|
||||
@ -2521,7 +2586,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
||||
if (call in Call.calls())
|
||||
acceptTransfer(ctx, viewModel, ua, call!!, ev[1])
|
||||
else
|
||||
makeCall(ctx, viewModel, ev[1], false)
|
||||
makeCall(ctx, viewModel, ev[1], viewModel.dialerState)
|
||||
}
|
||||
showDialog.value = true
|
||||
}
|
||||
@ -2530,7 +2595,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
|
||||
val call = Call.ofCallp(callp)
|
||||
if (call in Call.calls())
|
||||
Api.ua_hangup(uap, callp, 487, "Request Terminated")
|
||||
makeCall(ctx, viewModel, ev[1], false)
|
||||
makeCall(ctx, viewModel, ev[1], viewModel.dialerState)
|
||||
showCall(ctx, viewModel, ua)
|
||||
}
|
||||
"transfer failed" -> {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
@ -1323,6 +1324,26 @@ object Utils {
|
||||
return file
|
||||
}
|
||||
|
||||
fun pstnAccountHandle(ctx: Context): PhoneAccountHandle? {
|
||||
if (ctx.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) ==
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
val preferredHandle: PhoneAccountHandle? = tm.userSelectedOutgoingPhoneAccount
|
||||
if (preferredHandle != null)
|
||||
return preferredHandle
|
||||
}
|
||||
val baresipHandle = BaresipService.getPhoneAccountHandle(ctx)
|
||||
val phoneAccounts = tm.callCapablePhoneAccounts.filter { it != baresipHandle }
|
||||
return if (phoneAccounts.isNotEmpty())
|
||||
phoneAccounts[0]
|
||||
else
|
||||
null
|
||||
}
|
||||
else
|
||||
return null
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun listFilesInDirectory(directoryPath: String): List<File> {
|
||||
val directory = File(directoryPath)
|
||||
|
||||
@ -44,6 +44,7 @@ class ViewModel: ViewModel() {
|
||||
val callUriLabel: MutableState<String> = mutableStateOf(""),
|
||||
val showSuggestions: MutableState<Boolean> = mutableStateOf(false),
|
||||
val showCallButton: MutableState<Boolean> = mutableStateOf(true),
|
||||
val showCallPstnButton: MutableState<Boolean> = mutableStateOf(true),
|
||||
val showCallConferenceButton: MutableState<Boolean> = mutableStateOf(true),
|
||||
val callButtonsEnabled: MutableState<Boolean> = mutableStateOf(true),
|
||||
val conferenceCall: MutableState<Boolean> = mutableStateOf(false),
|
||||
|
||||
32
app/src/main/res/drawable/call_tel.xml
Normal file
32
app/src/main/res/drawable/call_tel.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
|
||||
<!-- Phone handle -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M798,840Q673,840 551,785.5Q429,731 329,631Q229,531 174.5,409Q120,287 120,162Q120,144 132,132Q144,120 162,120L324,120Q338,120 349,129.5Q360,139 362,152L388,292Q390,308 387,319Q384,330 376,338L279,436Q299,473 326.5,507.5Q354,542 387,574Q418,605 452,631.5Q486,658 524,680L618,586Q627,577 641.5,572.5Q656,568 670,570L808,598Q822,602 831,612.5Q840,623 840,636L840,798Q840,816 828,828Q816,840 798,840ZM241,360L307,294L290,200L201,200Q206,241 215,281Q224,321 241,360ZM599,718Q638,735 678.5,745Q719,755 760,758L760,670L666,651L599,718Z" />
|
||||
|
||||
<!-- Letter T -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M480,120L600,120L600,160L560,160L560,320L520,320L520,160L480,160Z" />
|
||||
|
||||
<!-- Letter E -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M620,120L740,120L740,160L660,160L660,200L730,200L730,240L660,240L660,280L740,280L740,320L620,320Z" />
|
||||
|
||||
<!-- Letter L -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M760,120L800,120L800,280L860,280L860,320L760,320Z" />
|
||||
|
||||
</vector>
|
||||
Reference in New Issue
Block a user