Resolved merge conflict in BaresipService.kt
This commit is contained in:
@ -5,77 +5,80 @@ import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.util.Locale
|
||||
|
||||
class Account(val accp: Long) {
|
||||
class Account(val accp: Long, virtualAor: String? = null) {
|
||||
|
||||
var isMobile = false
|
||||
var nickName = ""
|
||||
var displayName = Api.account_display_name(accp)
|
||||
val aor = Api.account_aor(accp)
|
||||
val luri = Api.account_luri(accp)
|
||||
var authUser = Api.account_auth_user(accp)
|
||||
var authPass = Api.account_auth_pass(accp)
|
||||
var displayName = if (accp != 0L) Api.account_display_name(accp) else ""
|
||||
val aor = if (accp != 0L) Api.account_aor(accp) else (virtualAor ?: "")
|
||||
val luri = if (accp != 0L) Api.account_luri(accp) else (virtualAor ?: "")
|
||||
var authUser = if (accp != 0L) Api.account_auth_user(accp) else ""
|
||||
var authPass = if (accp != 0L) Api.account_auth_pass(accp) else ""
|
||||
var outbound = ArrayList<String>()
|
||||
var mediaNat = Api.account_medianat(accp)
|
||||
var stunServer = Api.account_stun_uri(accp)
|
||||
var stunUser = Api.account_stun_user(accp)
|
||||
var stunPass = Api.account_stun_pass(accp)
|
||||
var mediaNat = if (accp != 0L) Api.account_medianat(accp) else ""
|
||||
var stunServer = if (accp != 0L) Api.account_stun_uri(accp) else ""
|
||||
var stunUser = if (accp != 0L) Api.account_stun_user(accp) else ""
|
||||
var stunPass = if (accp != 0L) Api.account_stun_pass(accp) else ""
|
||||
var audioCodec = ArrayList<String>()
|
||||
var videoCodec = ArrayList<String>()
|
||||
var regint = Api.account_regint(accp)
|
||||
var checkOrigin = Api.account_check_origin(accp)
|
||||
var regint = if (accp != 0L) Api.account_regint(accp) else 0
|
||||
var checkOrigin = if (accp != 0L) Api.account_check_origin(accp) else true
|
||||
var configuredRegInt = REGISTRATION_INTERVAL
|
||||
var mediaEnc = Api.account_mediaenc(accp)
|
||||
var rtcpMux = Api.account_rtcp_mux(accp)
|
||||
var rel100Mode = Api.account_rel100_mode(accp)
|
||||
var dtmfMode = Api.account_dtmfmode(accp)
|
||||
var answerMode = Api.account_answermode(accp)
|
||||
var autoRedirect = Api.account_sip_autoredirect(accp)
|
||||
var mediaEnc = if (accp != 0L) Api.account_mediaenc(accp) else ""
|
||||
var rtcpMux = if (accp != 0L) Api.account_rtcp_mux(accp) else false
|
||||
var rel100Mode = if (accp != 0L) Api.account_rel100_mode(accp) else Api.REL100_DISABLED
|
||||
var dtmfMode = if (accp != 0L) Api.account_dtmfmode(accp) else Api.DTMFMODE_AUTO
|
||||
var answerMode = if (accp != 0L) Api.account_answermode(accp) else Api.ANSWERMODE_MANUAL
|
||||
var autoRedirect = if (accp != 0L) Api.account_sip_autoredirect(accp) else false
|
||||
var blockUnknown = false
|
||||
var vmUri = Api.account_vm_uri(accp)
|
||||
var vmUri = if (accp != 0L) Api.account_vm_uri(accp) else ""
|
||||
var vmNew = 0
|
||||
var vmOld = 0
|
||||
var missedCalls = false
|
||||
var unreadMessages = false
|
||||
var callHistory = true
|
||||
var countryCode = ""
|
||||
var telProvider = Utils.aorDomain(aor)
|
||||
var telProvider = if (accp != 0L) Utils.aorDomain(aor) else ""
|
||||
var resumeUri = ""
|
||||
var numericKeypad = false
|
||||
var customParams = ""
|
||||
|
||||
init {
|
||||
if (accp != 0L) {
|
||||
if (authPass == "")
|
||||
authPass = NO_AUTH_PASS
|
||||
|
||||
if (authPass == "")
|
||||
authPass = NO_AUTH_PASS
|
||||
|
||||
var i = 0
|
||||
while (true) {
|
||||
val ob = Api.account_outbound(accp, i)
|
||||
if (ob != "") {
|
||||
outbound.add(ob)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
var i = 0
|
||||
while (true) {
|
||||
val ob = Api.account_outbound(accp, i)
|
||||
if (ob != "") {
|
||||
outbound.add(ob)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = 0
|
||||
while (true) {
|
||||
val ac = Api.account_audio_codec(accp, i)
|
||||
if (ac != "") {
|
||||
audioCodec.add(ac)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
i = 0
|
||||
while (true) {
|
||||
val ac = Api.account_audio_codec(accp, i)
|
||||
if (ac != "") {
|
||||
audioCodec.add(ac)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val extra = Api.account_extra(accp)
|
||||
if (Utils.paramExists(extra, "nickname"))
|
||||
nickName = Utils.paramValue(extra, "nickname")
|
||||
isMobile = Utils.paramExists(extra, "is_mobile")
|
||||
if (Utils.paramExists(extra, "regint"))
|
||||
configuredRegInt = Utils.paramValue(extra, "regint").toInt()
|
||||
callHistory = Utils.paramValue(extra, "call_history") == ""
|
||||
blockUnknown= Utils.paramExists(extra, "block_unknown")
|
||||
blockUnknown = Utils.paramExists(extra, "block_unknown")
|
||||
if (Utils.paramExists(extra, "country_code"))
|
||||
countryCode = Utils.paramValue(extra, "country_code")
|
||||
if (Utils.paramExists(extra, "tel_provider"))
|
||||
@ -86,14 +89,19 @@ class Account(val accp: Long) {
|
||||
|
||||
fun print() : String {
|
||||
|
||||
var res = if (displayName != "")
|
||||
"\"${displayName}\" "
|
||||
else
|
||||
""
|
||||
var res = if (isMobile) {
|
||||
"<${aor};transport=udp>"
|
||||
} else {
|
||||
if (displayName != "")
|
||||
"\"${displayName}\" "
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
res = "$res<$luri>"
|
||||
|
||||
if (authUser != "") res += ";auth_user=\"${authUser}\""
|
||||
if (!isMobile) {
|
||||
res = "$res<$luri>"
|
||||
if (authUser != "") res += ";auth_user=\"${authUser}\""
|
||||
}
|
||||
|
||||
if ((authPass != "") && !BaresipService.aorPasswords.containsKey(aor))
|
||||
res += ";auth_pass=\"${authPass}\""
|
||||
@ -155,13 +163,19 @@ class Account(val accp: Long) {
|
||||
if (autoRedirect)
|
||||
res += ";sip_autoredirect=yes"
|
||||
|
||||
res += ";ptime=20;regint=${regint};regq=0.5;pubint=0;inreq_allowed=yes;call_transfer=yes"
|
||||
res += ";ptime=20;regint=${regint};regq=0.5;pubint=0;inreq_allowed=yes"
|
||||
|
||||
if (isMobile)
|
||||
res += ";call_transfer=no"
|
||||
|
||||
var extra = ""
|
||||
|
||||
if (nickName != "")
|
||||
extra += ";nickname=${nickName}"
|
||||
|
||||
if (isMobile)
|
||||
extra += ";is_mobile=yes"
|
||||
|
||||
if (!callHistory)
|
||||
extra += ";call_history=no"
|
||||
|
||||
@ -271,7 +285,7 @@ class Account(val accp: Long) {
|
||||
BaresipService.filesPath + "/accounts",
|
||||
accounts.toByteArray(Charsets.UTF_8)
|
||||
)
|
||||
Log.d(TAG, "Saved accounts '${accounts}' to '${BaresipService.filesPath}/accounts'")
|
||||
// Log.d(TAG, "Saved accounts '${accounts}' to '${BaresipService.filesPath}/accounts'")
|
||||
}
|
||||
|
||||
fun ofAor(aor: String): Account? {
|
||||
|
||||
@ -232,14 +232,25 @@ private fun AccountContent(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
val aorText = if (ua.account.isMobile) {
|
||||
if (ua.account.aor == "sip:mobile@pstn")
|
||||
stringResource(R.string.not_available)
|
||||
else
|
||||
ua.account.aor
|
||||
} else
|
||||
ua.account.luri
|
||||
|
||||
OutlinedTextField(
|
||||
value = ua.account.luri,
|
||||
value = aorText,
|
||||
enabled = false,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = TextStyle(fontSize = 18.sp),
|
||||
label = {
|
||||
Text(text = stringResource(R.string.sip_uri), fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
text = stringResource(if (ua.account.isMobile) R.string.tel_uri else R.string.sip_uri),
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||
@ -1056,7 +1067,10 @@ private fun AccountContent(
|
||||
@Composable
|
||||
fun Voicemail() {
|
||||
val voicemailUriTitle = stringResource(R.string.voicemail_uri)
|
||||
val voicemailUriHelp = stringResource(R.string.voicemain_uri_help)
|
||||
val voicemailUriHelp = if (ua.account.isMobile)
|
||||
stringResource(R.string.voicemain_tel_uri_help)
|
||||
else
|
||||
stringResource(R.string.voicemain_uri_help)
|
||||
val vmUri by viewModel.vmUri.collectAsState()
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(end = 10.dp),
|
||||
@ -1247,37 +1261,43 @@ private fun AccountContent(
|
||||
) {
|
||||
AoR()
|
||||
Nickname()
|
||||
DisplayName()
|
||||
AuthUser()
|
||||
AuthPass()
|
||||
if (showPasswordDialog.value)
|
||||
AskPassword(ctx, navController, ua)
|
||||
Outbound()
|
||||
Register()
|
||||
if (viewModel.register.collectAsState().value) {
|
||||
RegInt()
|
||||
CheckOrigin()
|
||||
if (!ua.account.isMobile) {
|
||||
DisplayName()
|
||||
AuthUser()
|
||||
AuthPass()
|
||||
if (showPasswordDialog.value)
|
||||
AskPassword(ctx, navController, ua)
|
||||
Outbound()
|
||||
Register()
|
||||
if (viewModel.register.collectAsState().value) {
|
||||
RegInt()
|
||||
CheckOrigin()
|
||||
}
|
||||
}
|
||||
BlockUnknown()
|
||||
AudioCodecs(navController, aor)
|
||||
MediaEnc()
|
||||
MediaNat()
|
||||
if (showStun) {
|
||||
StunServer()
|
||||
StunUser()
|
||||
StunPass()
|
||||
if (!ua.account.isMobile) {
|
||||
AudioCodecs(navController, aor)
|
||||
MediaEnc()
|
||||
MediaNat()
|
||||
if (showStun) {
|
||||
StunServer()
|
||||
StunUser()
|
||||
StunPass()
|
||||
}
|
||||
RtcpMux()
|
||||
Rel100()
|
||||
Dtmf()
|
||||
Redirect()
|
||||
Answer()
|
||||
}
|
||||
RtcpMux()
|
||||
Rel100()
|
||||
Dtmf()
|
||||
Answer()
|
||||
Redirect()
|
||||
Voicemail()
|
||||
CountryCode()
|
||||
TelProvider()
|
||||
if (!ua.account.isMobile)
|
||||
TelProvider()
|
||||
NumericKeypad()
|
||||
DefaultAccount()
|
||||
CustomParams()
|
||||
if (!ua.account.isMobile)
|
||||
CustomParams()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1598,8 +1618,13 @@ private fun checkOnClick(ctx: Context, viewModel: AccountViewModel, ua: UserAgen
|
||||
var newVmUri = viewModel.vmUri.value.trim()
|
||||
if (newVmUri != acc.vmUri) {
|
||||
if (newVmUri != "") {
|
||||
if (!newVmUri.startsWith("sip:")) newVmUri = "sip:$newVmUri"
|
||||
if (!newVmUri.contains("@")) newVmUri = "$newVmUri@${acc.host()}"
|
||||
if (acc.isMobile) {
|
||||
if (!newVmUri.startsWith("tel:")) newVmUri = "tel:$newVmUri"
|
||||
}
|
||||
else {
|
||||
if (!newVmUri.startsWith("sip:")) newVmUri = "sip:$newVmUri"
|
||||
if (!newVmUri.contains("@")) newVmUri = "$newVmUri@${acc.host()}"
|
||||
}
|
||||
if (!Utils.checkUri(newVmUri)) {
|
||||
alertTitle.value = noticeTitle
|
||||
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri),
|
||||
@ -1654,7 +1679,7 @@ private fun checkOnClick(ctx: Context, viewModel: AccountViewModel, ua: UserAgen
|
||||
|
||||
if (viewModel.defaultAccount.value) ua.makeDefault()
|
||||
|
||||
Api.account_debug(acc.accp)
|
||||
// Api.account_debug(acc.accp)
|
||||
|
||||
Account.saveAccounts()
|
||||
|
||||
|
||||
@ -164,8 +164,6 @@ private var newOpusPacketLoss = oldOpusPacketLoss
|
||||
private var newAudioDelay = BaresipService.audioDelay.toString()
|
||||
private var newToneCountry = BaresipService.toneCountry
|
||||
private var newRingtoneUri = ""
|
||||
private var oldTelecom = BaresipService.telecom
|
||||
private var newTelecom = oldTelecom
|
||||
|
||||
private var save = false
|
||||
|
||||
@ -178,8 +176,6 @@ private fun AudioContent(contentPadding: PaddingValues) {
|
||||
|
||||
oldSpeakerPhone = Config.variable("speaker_phone") == "yes"
|
||||
newSpeakerPhone = oldSpeakerPhone
|
||||
oldTelecom = Config.variable("telecom") == "yes"
|
||||
newTelecom = oldTelecom
|
||||
oldAudioModules = Config.variables("module")
|
||||
oldOpusBitrate = Config.variable("opus_bitrate")
|
||||
oldOpusPacketLoss = Config.variable("opus_packet_loss")
|
||||
@ -206,7 +202,6 @@ private fun AudioContent(contentPadding: PaddingValues) {
|
||||
.verticalScroll(state = scrollState),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Telecom()
|
||||
Ringtone()
|
||||
ToneCountry()
|
||||
SpeakerPhone()
|
||||
@ -219,37 +214,6 @@ private fun AudioContent(contentPadding: PaddingValues) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Telecom() {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
val telecomTitle = stringResource(R.string.telecom)
|
||||
val telecomHelp = stringResource(R.string.telecom_help)
|
||||
Text(text = telecomTitle,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clickable {
|
||||
alertTitle.value = telecomTitle
|
||||
alertMessage.value = telecomHelp
|
||||
showAlert.value = true
|
||||
},
|
||||
fontSize = 18.sp)
|
||||
var telecom by remember { mutableStateOf(oldTelecom) }
|
||||
Switch(
|
||||
checked = telecom,
|
||||
onCheckedChange = {
|
||||
telecom = it
|
||||
newTelecom = telecom
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Ringtone() {
|
||||
val ringToneTitle = stringResource(R.string.ringtone)
|
||||
@ -629,13 +593,6 @@ private fun checkOnClick(ctx: Context): Result {
|
||||
|
||||
var restart = false
|
||||
|
||||
if (newTelecom != oldTelecom) {
|
||||
Config.replaceVariable("telecom", if (newTelecom) "yes" else "no")
|
||||
BaresipService.telecom = newTelecom
|
||||
restart = true
|
||||
save = true
|
||||
}
|
||||
|
||||
if (Preferences(ctx).ringtoneUri != newRingtoneUri) {
|
||||
Preferences(ctx).ringtoneUri = newRingtoneUri
|
||||
BaresipService.rt = RingtoneManager.getRingtone(ctx, newRingtoneUri.toUri())
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.Manifest
|
||||
import android.Manifest.permission.RECORD_AUDIO
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
@ -61,7 +60,6 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.MessagingStyle
|
||||
import androidx.core.app.Person
|
||||
@ -70,6 +68,7 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.tutpro.baresip.Utils.e164Uri
|
||||
import com.tutpro.baresip.Utils.toCircle
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -491,18 +490,7 @@ class BaresipService: Service() {
|
||||
activeNetwork = cm.activeNetwork
|
||||
Log.i(TAG, "Active network: $activeNetwork")
|
||||
|
||||
if (telecom)
|
||||
registerPhoneAccount()
|
||||
else
|
||||
if (btAdapter != null) {
|
||||
Log.i(TAG, "Registering bluetooth receiver")
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
|
||||
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)
|
||||
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
|
||||
this.registerReceiver(bluetoothReceiver, filter)
|
||||
bluetoothReceiverRegistered = true
|
||||
}
|
||||
registerPhoneAccount()
|
||||
|
||||
Log.i(TAG, "AEC/AGC/NS available = $aecAvailable/$agcAvailable/$nsAvailable")
|
||||
|
||||
@ -544,16 +532,14 @@ class BaresipService: Service() {
|
||||
}
|
||||
|
||||
"Call Answer" -> {
|
||||
val uap = intent!!.getLongExtra("uap", 0L)
|
||||
val callp = intent.getLongExtra("callp", 0L)
|
||||
val callp = intent!!.getLongExtra("callp", 0L)
|
||||
val call = Call.ofCallp(callp)
|
||||
stopRinging()
|
||||
stopMediaPlayer()
|
||||
setCallVolume()
|
||||
proximitySensing(proximitySensing)
|
||||
Api.ua_answer(uap, callp, Api.VIDMODE_OFF)
|
||||
call?.answer()
|
||||
updateStatusNotification()
|
||||
if (!telecom)
|
||||
ensureCommunicationMode()
|
||||
}
|
||||
|
||||
"Call Reject" -> {
|
||||
@ -566,20 +552,15 @@ class BaresipService: Service() {
|
||||
val aor = call.ua.account.aor
|
||||
Log.d(TAG, "Aor $aor rejected incoming call $callp from $peerUri")
|
||||
call.rejected = true
|
||||
Api.ua_hangup(call.ua.uap, callp, 486, "Rejected")
|
||||
call.reject()
|
||||
}
|
||||
}
|
||||
|
||||
"Call Hangup" -> {
|
||||
val callp = intent!!.getLongExtra("callp", 0L)
|
||||
Log.d(TAG, "onStartCommand Hangup action for $callp")
|
||||
val connection = ConnectionService.connections[callp]
|
||||
if (connection != null) {
|
||||
connection.onDisconnect() // This calls Api.ua_hangup(..., 0, "")
|
||||
} else {
|
||||
val call = Call.ofCallp(callp)
|
||||
if (call != null) Api.ua_hangup(call.ua.uap, callp, 0, "")
|
||||
}
|
||||
val call = Call.ofCallp(callp)
|
||||
call?.hangup(0, "")
|
||||
}
|
||||
|
||||
"Transfer Deny" -> {
|
||||
@ -731,7 +712,9 @@ class BaresipService: Service() {
|
||||
if (ev[0] == "create") {
|
||||
|
||||
val ua = UserAgent(uap)
|
||||
ua.status = if (ua.account.regint == 0)
|
||||
ua.status = if (ua.account.isMobile)
|
||||
R.drawable.circle_green
|
||||
else if (ua.account.regint == 0)
|
||||
R.drawable.circle_white
|
||||
else
|
||||
circleYellow.getValue(colorblind)
|
||||
@ -876,21 +859,15 @@ class BaresipService: Service() {
|
||||
break
|
||||
speakerPhone = speakerPhoneAuto
|
||||
stopMediaPlayer()
|
||||
val hasTelecom = ConnectionService.connections.containsKey(callp) ||
|
||||
ConnectionService.pendingOutgoingConnection != null
|
||||
if (!hasTelecom) {
|
||||
setCallVolume()
|
||||
ensureCommunicationMode()
|
||||
}
|
||||
setCallVolume()
|
||||
ensureCommunicationMode()
|
||||
proximitySensing(proximitySensing)
|
||||
}
|
||||
"call ringing" -> {
|
||||
if (!telecom) ensureCommunicationMode()
|
||||
playRingBack()
|
||||
return
|
||||
}
|
||||
"call progress" -> {
|
||||
if (!telecom) ensureCommunicationMode()
|
||||
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
|
||||
stopMediaPlayer()
|
||||
else {
|
||||
@ -934,7 +911,6 @@ class BaresipService: Service() {
|
||||
)
|
||||
if (resourceId != 0) {
|
||||
ensureCommunicationMode()
|
||||
if (!telecom) playUnInterrupted(resourceId, 1)
|
||||
} else {
|
||||
Log.e(TAG, "Callwaiting tone $name.wav not found")
|
||||
}
|
||||
@ -954,23 +930,14 @@ class BaresipService: Service() {
|
||||
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
|
||||
if (Call.ofCallp(callp) == null)
|
||||
Call(callp, ua, peerUri, "in", "incoming").add()
|
||||
if (telecom) {
|
||||
val extras = android.os.Bundle()
|
||||
extras.putLong("uap", uap)
|
||||
extras.putLong("callp", callp)
|
||||
extras.putString("peerUri", peerUri)
|
||||
try {
|
||||
tm.addNewIncomingCall(getPhoneAccountHandle(this), extras)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Telecom addNewIncomingCall failed: ${e.message}")
|
||||
}
|
||||
} else {
|
||||
if (!requestAudioFocus(applicationContext)) {
|
||||
Log.w(TAG, "Audio focus denied for incoming call")
|
||||
Api.ua_hangup(uap, callp, 486, "Busy Here")
|
||||
return
|
||||
}
|
||||
handleIncomingCall(Call.ofCallp(callp)!!)
|
||||
val extras = android.os.Bundle()
|
||||
extras.putLong("uap", uap)
|
||||
extras.putLong("callp", callp)
|
||||
extras.putString("peerUri", peerUri)
|
||||
try {
|
||||
tm.addNewIncomingCall(getPhoneAccountHandle(this), extras)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Telecom addNewIncomingCall failed: ${e.message}")
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1031,12 +998,6 @@ class BaresipService: Service() {
|
||||
if (call.state() == Api.CALL_STATE_EARLY) {
|
||||
if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)
|
||||
stopMediaPlayer()
|
||||
else {
|
||||
if (!telecom) {
|
||||
ConnectionService.connections[callp]?.setRinging()
|
||||
playRingBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.status.value == "connected" && !call.held && !call.onhold) {
|
||||
if (call.callOnHold.value || call.showOnHoldNotice.value) {
|
||||
@ -1111,11 +1072,8 @@ class BaresipService: Service() {
|
||||
if (call != null) {
|
||||
call.terminated.value = true
|
||||
call.remove()
|
||||
if (!Call.inCall()) {
|
||||
if (!Call.inCall())
|
||||
proximitySensing(false)
|
||||
if (!telecom)
|
||||
abandonAudioFocus(applicationContext)
|
||||
}
|
||||
}
|
||||
ConnectionService.lastDisconnectTime = System.currentTimeMillis()
|
||||
val connection = ConnectionService.connections[callp]
|
||||
@ -1174,8 +1132,7 @@ class BaresipService: Service() {
|
||||
if (tone == "busy")
|
||||
playBusy()
|
||||
else
|
||||
if (!Call.inCall())
|
||||
ensureCommunicationMode()
|
||||
ensureCommunicationMode()
|
||||
if (call.dir == "out")
|
||||
call.rejected = call.startTime == null &&
|
||||
!reason.startsWith("408") &&
|
||||
@ -1473,9 +1430,7 @@ class BaresipService: Service() {
|
||||
}, audioDelay)
|
||||
}
|
||||
|
||||
val isTelecom = Call.calls().any { ConnectionService.connections.containsKey(it.callp) } ||
|
||||
ConnectionService.pendingOutgoingConnection != null
|
||||
if (isTelecom)
|
||||
if (Call.hasTelecomCall())
|
||||
executeCall()
|
||||
else if (VERSION.SDK_INT < 31) {
|
||||
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
|
||||
@ -1511,6 +1466,7 @@ class BaresipService: Service() {
|
||||
fun started() {
|
||||
Log.d(TAG, "Received 'started' from baresip")
|
||||
isNativeReady = true
|
||||
addMobileUserAgent()
|
||||
Api.net_debug()
|
||||
postServiceEvent(ServiceEvent("started", arrayListOf(callActionUri), System.nanoTime()))
|
||||
callActionUri = ""
|
||||
@ -1747,6 +1703,178 @@ class BaresipService: Service() {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleExternalCall(telecomCall: android.telecom.Call, preferredAor: String? = null) {
|
||||
val rawUri = telecomCall.details.handle?.toString() ?: "Unknown"
|
||||
val uri = try {
|
||||
java.net.URLDecoder.decode(rawUri, "UTF-8")
|
||||
} catch (_: Exception) {
|
||||
rawUri
|
||||
}
|
||||
|
||||
if (uas.value.isEmpty()) {
|
||||
Log.e(TAG, "No User Agents available to handle external call")
|
||||
return
|
||||
}
|
||||
|
||||
val ua = preferredAor?.let { UserAgent.ofAor(it) }
|
||||
?: uas.value.find { it.account.isMobile }
|
||||
?: uas.value[0]
|
||||
|
||||
val telecomState = if (VERSION.SDK_INT >= 31)
|
||||
telecomCall.details.state
|
||||
else
|
||||
@Suppress("DEPRECATION") telecomCall.state
|
||||
|
||||
val isIncoming = telecomState == android.telecom.Call.STATE_RINGING
|
||||
Log.d(TAG, "Handling external call ${if (isIncoming) "from" else "to"} $uri (preferredAor=$preferredAor)")
|
||||
|
||||
val initialStatus = when (telecomState) {
|
||||
android.telecom.Call.STATE_RINGING -> "incoming"
|
||||
android.telecom.Call.STATE_DIALING, android.telecom.Call.STATE_CONNECTING -> "outgoing"
|
||||
else -> "connected"
|
||||
}
|
||||
|
||||
if (isIncoming) {
|
||||
val e164Uri = e164Uri(uri, ua.account.countryCode)
|
||||
if (ua.account.blockUnknown && Contact.contactName(e164Uri) == e164Uri) {
|
||||
Log.d(TAG, "Auto-rejecting incoming PSTN call from $uri")
|
||||
telecomCall.disconnect()
|
||||
toast(String.format(getString(R.string.call_blocked),
|
||||
Utils.friendlyUri(this, uri, ua.account)))
|
||||
if (ua.account.callHistory) {
|
||||
Blocked(
|
||||
ua.account.aor,
|
||||
uri,
|
||||
"invite",
|
||||
GregorianCalendar().timeInMillis
|
||||
).add()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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, android.telecom.Call.STATE_DISCONNECTING -> "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
|
||||
if (newStatus == "connected")
|
||||
it.startTime = GregorianCalendar()
|
||||
postServiceEvent(ServiceEvent(
|
||||
"call update",
|
||||
arrayListOf(it.ua.uap, it.callp),
|
||||
System.nanoTime())
|
||||
)
|
||||
if (newStatus == "closed")
|
||||
handleExternalCallRemoved(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
calls.add(call)
|
||||
setCallVolume()
|
||||
ensureCommunicationMode()
|
||||
postServiceEvent(ServiceEvent(
|
||||
if (isIncoming) "call incoming" else "call outgoing",
|
||||
arrayListOf(ua.uap, call.callp),
|
||||
System.nanoTime())
|
||||
)
|
||||
}
|
||||
|
||||
fun handleExternalCallRemoved(telecomCall: android.telecom.Call) {
|
||||
val callp = telecomCall.hashCode().toLong()
|
||||
val call = calls.find { it.callp == callp }
|
||||
if (call != null) {
|
||||
if (call.ua.account.callHistory) {
|
||||
val historyPeerUri = e164Uri(call.peerUri, call.ua.account.countryCode)
|
||||
val history = CallHistoryNew(call.ua.account.aor, historyPeerUri, call.dir)
|
||||
history.stopTime = GregorianCalendar()
|
||||
history.startTime = call.startTime
|
||||
history.rejected = call.rejected
|
||||
history.add()
|
||||
if (call.dir == "in" && call.startTime == null && !call.rejected)
|
||||
call.ua.account.missedCalls = true
|
||||
}
|
||||
calls.remove(call)
|
||||
}
|
||||
if (!Call.inCall()) {
|
||||
proximitySensing(false)
|
||||
stopMediaPlayer()
|
||||
}
|
||||
messageUpdate.postValue(System.currentTimeMillis())
|
||||
}
|
||||
|
||||
fun addMobileUserAgent() {
|
||||
if (VERSION.SDK_INT < 29) return
|
||||
|
||||
val mobileAccountHandle = Utils.pstnAccountHandle(this)
|
||||
val existingMobileUa = uas.value.find { it.account.isMobile }
|
||||
|
||||
// If mobile account should not exist (role lost or no SIM), remove it if it exists
|
||||
if (mobileAccountHandle == null) {
|
||||
if (existingMobileUa != null) {
|
||||
Log.d(TAG, "Removing Mobile account (role lost or SIM missing)")
|
||||
existingMobileUa.remove()
|
||||
Account.saveAccounts()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val userPart = Utils.getLine1Number(this) ?: "mobile"
|
||||
val mobileAor = "sip:$userPart@pstn"
|
||||
|
||||
if (existingMobileUa != null) {
|
||||
// Update AOR if it previously was sip:mobile@pstn but now a real number
|
||||
if (existingMobileUa.account.aor == "sip:mobile@pstn" && mobileAor != "sip:mobile@pstn") {
|
||||
Log.d(TAG, "Updating existing Mobile account AOR to $mobileAor")
|
||||
val aorField = Account::class.java.getDeclaredField("aor")
|
||||
aorField.isAccessible = true
|
||||
aorField.set(existingMobileUa.account, mobileAor)
|
||||
val luriField = Account::class.java.getDeclaredField("luri")
|
||||
luriField.isAccessible = true
|
||||
luriField.set(existingMobileUa.account, mobileAor)
|
||||
Account.saveAccounts()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Injecting new virtual Mobile account: $mobileAor")
|
||||
val account = Account(0L, mobileAor)
|
||||
account.isMobile = true
|
||||
account.nickName = "Mobile"
|
||||
account.regint = 0
|
||||
account.telProvider = ""
|
||||
val mobileUa = UserAgent(0L, account)
|
||||
|
||||
val updatedUas = uas.value.toMutableList()
|
||||
updatedUas.add(mobileUa)
|
||||
uas.value = updatedUas.toList()
|
||||
uasStatus.value = UserAgent.statusMap()
|
||||
|
||||
Account.saveAccounts()
|
||||
}
|
||||
|
||||
private fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(this@BaresipService.applicationContext, message, length).show()
|
||||
@ -2048,8 +2176,7 @@ class BaresipService: Service() {
|
||||
cleanupRunnable = null
|
||||
}
|
||||
if (isSpeakerphoneOn == speakerPhone) {
|
||||
val hasTelecom = Call.calls().any { ConnectionService.connections.containsKey(it.callp) }
|
||||
if (hasTelecom || currentMode == MODE_IN_COMMUNICATION) {
|
||||
if (Call.hasTelecomCall() || currentMode == MODE_IN_COMMUNICATION) {
|
||||
Log.d(TAG, "Already in valid call mode ($currentMode) with correct speaker state.")
|
||||
return
|
||||
}
|
||||
@ -2076,13 +2203,17 @@ class BaresipService: Service() {
|
||||
val runnable = Runnable {
|
||||
cleanupRunnable = null
|
||||
if (Call.inCall()) {
|
||||
val hasTelecom = Call.calls().any { ConnectionService.connections.containsKey(it.callp) }
|
||||
val hasTelecom = Call.hasTelecomCall()
|
||||
if (!hasTelecom && am.mode != MODE_IN_COMMUNICATION && am.mode != AudioManager.MODE_IN_CALL) {
|
||||
am.mode = MODE_IN_COMMUNICATION
|
||||
Log.d(TAG, "Manual Mode Guard: Setting MODE_IN_COMMUNICATON from ${am.mode}")
|
||||
}
|
||||
Log.d(TAG, "Applying speakerphone state: $speakerPhone")
|
||||
if (!hasTelecom) {
|
||||
if (InCallService.instance != null) {
|
||||
Log.d(TAG, "Using InCallService for audio route: $speakerPhone")
|
||||
@Suppress("DEPRECATION")
|
||||
InCallService.instance!!.setAudioRoute(if (speakerPhone) android.telecom.CallAudioState.ROUTE_SPEAKER else android.telecom.CallAudioState.ROUTE_EARPIECE)
|
||||
} else if (!hasTelecom) {
|
||||
Log.d(TAG, "No Telecom connection, using AudioManager for speaker")
|
||||
Utils.setSpeakerPhone(mainExecutor, am, speakerPhone)
|
||||
} else {
|
||||
@ -2117,7 +2248,7 @@ class BaresipService: Service() {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!Call.calls().any { ConnectionService.connections.containsKey(it.callp) })
|
||||
if (!Call.hasTelecomCall())
|
||||
resetCallVolume()
|
||||
}
|
||||
}
|
||||
@ -2377,7 +2508,6 @@ class BaresipService: Service() {
|
||||
var isRecOn = false
|
||||
var toneCountry = "us"
|
||||
var proximitySensing = true
|
||||
var telecom = true
|
||||
|
||||
val uas = mutableStateOf(emptyList<UserAgent>())
|
||||
val uasStatus = mutableStateOf(emptyMap<String, Int>())
|
||||
@ -2471,8 +2601,6 @@ class BaresipService: Service() {
|
||||
) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
||||
) {
|
||||
Log.d(TAG, "requestAudioFocus granted")
|
||||
if (!telecom && isBluetoothHeadsetConnected(ctx))
|
||||
startBluetoothSco(ctx, 250L, 3)
|
||||
}
|
||||
else {
|
||||
Log.w(TAG, "requestAudioFocus denied")
|
||||
@ -2481,39 +2609,6 @@ class BaresipService: Service() {
|
||||
return audioFocusRequest != null
|
||||
}
|
||||
|
||||
fun abandonAudioFocus(ctx: Context) {
|
||||
val am = ctx.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
if (audioFocusRequest != null) {
|
||||
Log.d(TAG, "Abandoning audio focus")
|
||||
if (androidx.media.AudioManagerCompat.abandonAudioFocusRequest(
|
||||
am,
|
||||
audioFocusRequest!!
|
||||
) ==
|
||||
AudioManager.AUDIOFOCUS_REQUEST_GRANTED
|
||||
) {
|
||||
audioFocusRequest = null
|
||||
if (!telecom && isBluetoothHeadsetConnected(ctx))
|
||||
stopBluetoothSco(ctx)
|
||||
}
|
||||
else
|
||||
Log.e(TAG, "Failed to abandon audio focus")
|
||||
}
|
||||
am.mode = MODE_NORMAL
|
||||
}
|
||||
|
||||
private fun isBluetoothHeadsetConnected(ctx: Context): Boolean {
|
||||
if (VERSION.SDK_INT >= 31 &&
|
||||
ActivityCompat.checkSelfPermission(
|
||||
ctx,
|
||||
Manifest.permission.BLUETOOTH_CONNECT
|
||||
) == PackageManager.PERMISSION_DENIED
|
||||
)
|
||||
return false
|
||||
return btAdapter != null && btAdapter!!.isEnabled &&
|
||||
btAdapter!!.getProfileConnectionState(BluetoothHeadset.HEADSET) ==
|
||||
BluetoothAdapter.STATE_CONNECTED
|
||||
}
|
||||
|
||||
private fun isBluetoothScoOn(am: AudioManager): Boolean {
|
||||
return if (VERSION.SDK_INT < 31)
|
||||
@Suppress("DEPRECATION")
|
||||
|
||||
@ -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,81 @@ 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)
|
||||
}
|
||||
|
||||
open fun hangup(code: Int, reason: String) {
|
||||
val connection = ConnectionService.connections[callp]
|
||||
if (connection != null)
|
||||
connection.onDisconnect()
|
||||
else
|
||||
Api.ua_hangup(ua.uap, callp, code, reason)
|
||||
}
|
||||
|
||||
open fun answer() {
|
||||
Api.ua_answer(ua.uap, callp, Api.VIDMODE_OFF)
|
||||
}
|
||||
|
||||
open fun reject() {
|
||||
hangup(486, "Busy Here")
|
||||
}
|
||||
|
||||
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 answer() {
|
||||
telecomCall.answer(android.telecom.VideoProfile.STATE_AUDIO_ONLY)
|
||||
}
|
||||
|
||||
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 hangup(code: Int, reason: String) {
|
||||
telecomCall.disconnect()
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
telecomCall.disconnect()
|
||||
}
|
||||
|
||||
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> {
|
||||
@ -172,6 +243,12 @@ class Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: Str
|
||||
return BaresipService.calls.isNotEmpty()
|
||||
}
|
||||
|
||||
fun hasTelecomCall(): Boolean {
|
||||
return BaresipService.calls.any {
|
||||
it is ExternalCall || ConnectionService.connections.containsKey(it.callp)
|
||||
} || ConnectionService.pendingOutgoingConnection != null
|
||||
}
|
||||
|
||||
fun isAnyCallActive(ctx: Context): Boolean {
|
||||
// Check if there exist SIP calls that are not onhold or held
|
||||
if (BaresipService.calls.any { !it.onhold && !it.held }) return true
|
||||
|
||||
@ -226,13 +226,6 @@ object Config {
|
||||
BaresipService.toneCountry = toneCountry
|
||||
config = "${config}tone_country ${BaresipService.toneCountry}\n"
|
||||
|
||||
val telecom = previousVariable("telecom")
|
||||
if (telecom != "")
|
||||
BaresipService.telecom = telecom == "yes"
|
||||
else
|
||||
BaresipService.telecom = true
|
||||
config = "${config}telecom ${if (BaresipService.telecom) "yes" else "no"}\n"
|
||||
|
||||
save()
|
||||
BaresipService.isConfigInitialized = true
|
||||
|
||||
|
||||
@ -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,53 @@
|
||||
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
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class InCallService : InCallService() {
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
instance = this
|
||||
return super.onBind(intent)
|
||||
}
|
||||
|
||||
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")
|
||||
// SIP call is already managed by ConnectionService/BaresipService
|
||||
} else {
|
||||
Log.d(TAG, "InCallService: Identified as PSTN call from $handle")
|
||||
val aor = call.details.intentExtras?.getString("aor")
|
||||
BaresipService.instance?.handleExternalCall(call, aor)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
override fun onCallRemoved(call: Call) {
|
||||
super.onCallRemoved(call)
|
||||
Log.d(TAG, "InCallService: Call removed")
|
||||
BaresipService.instance?.handleExternalCallRemoved(call)
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
instance = null
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Baresip"
|
||||
private var _instance = WeakReference<InCallService>(null)
|
||||
var instance: InCallService?
|
||||
get() = _instance.get()
|
||||
set(value) {
|
||||
_instance = WeakReference(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1031,6 +1031,7 @@ private fun SettingsContent(
|
||||
) { result ->
|
||||
Log.d(TAG, "dialerRoleRequest result: $result")
|
||||
viewModel.defaultDialer.value = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)
|
||||
BaresipService.instance?.addMobileUserAgent()
|
||||
}
|
||||
Switch(
|
||||
checked = defaultDialer,
|
||||
@ -1181,14 +1182,14 @@ private fun SettingsContent(
|
||||
UserAgent()
|
||||
UniqueContactUri()
|
||||
AudioSettings(navController)
|
||||
if (VERSION.SDK_INT >= 29)
|
||||
DefaultDialer()
|
||||
BatteryOptimizations()
|
||||
DarkTheme()
|
||||
if (VERSION.SDK_INT >= 31)
|
||||
DynamicColors()
|
||||
ColorBlind()
|
||||
ProximitySensing()
|
||||
if (VERSION.SDK_INT >= 29)
|
||||
DefaultDialer()
|
||||
Debug()
|
||||
SipTrace()
|
||||
Reset(onRestartApp)
|
||||
|
||||
@ -5,12 +5,13 @@ import com.tutpro.baresip.BaresipService.Companion.colorblind
|
||||
import com.tutpro.baresip.BaresipService.Companion.uas
|
||||
import com.tutpro.baresip.BaresipService.Companion.uasStatus
|
||||
|
||||
class UserAgent(val uap: Long) {
|
||||
class UserAgent(val uap: Long, virtualAccount: Account? = null) {
|
||||
|
||||
val account = Account(Api.ua_account(uap))
|
||||
var status = R.drawable.circle_white
|
||||
val account = virtualAccount ?: Account(Api.ua_account(uap))
|
||||
var status = if (uap != 0L) R.drawable.circle_white else R.drawable.circle_green
|
||||
|
||||
fun callAlloc(xCall: Long, videoMode: Int): Long {
|
||||
if (uap == 0L) return 0L
|
||||
return Api.ua_call_alloc(uap, xCall, videoMode)
|
||||
}
|
||||
|
||||
@ -49,6 +50,7 @@ class UserAgent(val uap: Long) {
|
||||
}
|
||||
|
||||
fun reRegister() {
|
||||
if (uap == 0L) return
|
||||
this.status = circleYellow.getValue(colorblind)
|
||||
if (this.account.regint == 0)
|
||||
Api.ua_unregister(this.uap)
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
package com.tutpro.baresip
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
import android.app.role.RoleManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Context.ROLE_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
@ -26,6 +29,8 @@ import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import android.provider.OpenableColumns
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.telecom.TelecomManager
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.text.format.DateUtils
|
||||
@ -128,7 +133,10 @@ object Utils {
|
||||
return if (uri.contains("@"))
|
||||
uri.substringAfter(":").substringBefore("@")
|
||||
else
|
||||
""
|
||||
if (isTelUri(uri))
|
||||
uri.substringAfter(":").substringBefore(";")
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
fun uriMatch(firstUri: String, secondUri: String): Boolean {
|
||||
@ -180,14 +188,14 @@ object Utils {
|
||||
return u
|
||||
}
|
||||
|
||||
private fun e164Uri(uri: String, countryCode: String): String {
|
||||
fun e164Uri(uri: String, countryCode: String): String {
|
||||
if (countryCode == "") return uri
|
||||
val scheme = uri.take(4)
|
||||
val userPart = uriUserPart(uri)
|
||||
return if (userPart.isDigitsOnly()) {
|
||||
when {
|
||||
userPart.startsWith("00") -> uri.replace("$scheme$userPart",
|
||||
scheme + userPart.substring(2))
|
||||
scheme + "+" + userPart.substring(2))
|
||||
userPart.startsWith("0") -> uri.replace("${scheme}0",
|
||||
"$scheme$countryCode")
|
||||
else -> uri.replace(scheme, "$scheme$countryCode")
|
||||
@ -1332,6 +1340,57 @@ object Utils {
|
||||
return file
|
||||
}
|
||||
|
||||
@RequiresApi(29)
|
||||
fun pstnAccountHandle(ctx: Context): PhoneAccountHandle? {
|
||||
val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager
|
||||
if (ctx.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
roleManager.isRoleHeld(RoleManager.ROLE_DIALER)) {
|
||||
val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
|
||||
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
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
fun getLine1Number(ctx: Context): String? {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED) {
|
||||
val sm = ctx.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
|
||||
val number = sm.getPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
|
||||
if (number != "") {
|
||||
Log.i(TAG, "Retrieved SIM number via SubscriptionManager")
|
||||
return number
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ContextCompat.checkSelfPermission(ctx, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED ||
|
||||
ContextCompat.checkSelfPermission(ctx, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
|
||||
val tm = ctx.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
@Suppress("DEPRECATION")
|
||||
val number = tm.line1Number
|
||||
if (number != null) {
|
||||
Log.i(TAG, "Retrieved SIM number via TelephonyManager")
|
||||
return number
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "getLine1Number failed: ${e.message}")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun listFilesInDirectory(directoryPath: String): List<File> {
|
||||
val directory = File(directoryPath)
|
||||
|
||||
@ -109,6 +109,7 @@
|
||||
</string>
|
||||
<!-- Account Activity -->
|
||||
<string name="account">Tili</string>
|
||||
<string name="not_available">Ei saatavana</string>
|
||||
<string name="account_nickname_help">Lempinimi (jos annettu) millä tämä tili identifioidaan
|
||||
baresip sovelluksessa.</string>
|
||||
<string name="nickname">Lempinimi</string>
|
||||
@ -215,15 +216,16 @@
|
||||
<string name="redirect_mode_help">Valitsee toteutetaanko puhelun uudelleenohjauspyyntö
|
||||
automaattisesti vai kysytäänkö vahvistusta.</string>
|
||||
<string name="voicemail_uri">Puhepostin URI</string>
|
||||
<string name="voicemain_uri_help">SIP URI, jota käytetään
|
||||
<string name="voicemain_uri_help">SIP URI jota käytetään
|
||||
puhepostiviestien kuunteluun. Jos URI:a ei ole annettu, tietoa
|
||||
mahdollista puhepostiviesteistä (Message Waiting Indications) ei tilata.
|
||||
</string>
|
||||
<string name="voicemain_tel_uri_help">TEL URI jota käytetään puhepostiviestien kuunteluun.</string>
|
||||
<string name="country_code">Maakoodi</string>
|
||||
<string name="country_code_help">Tämän tilin E.164-maakoodi. Jos tulevan puhelun tai viestin
|
||||
From URI:n käyttäjäosa sisältää puhelinnumeron, joka ei ala \'+\' merkillä, ja jos sitä
|
||||
<string name="country_code_help">Tämän tilin E.164-maakoodi. Jos puhelun tai viestin toisen
|
||||
osapuolen URI:n käyttäjäosa sisältää puhelinnumeron, joka ei ala \'+\' merkillä, ja jos sitä
|
||||
ei löydy yhteystiedoista, niin tämä maakoodi lisätään numeron eteen ja etsintä tehdään
|
||||
uudelleen. Jos puhelinnumero alkaa yhdellä numerolla \'0\', niin numero \'0\'
|
||||
uudelleen. Jos puhelinnumero alkaa yhdellä tai kahdella numerolla \'0\', niin ne
|
||||
poistetaan ennen maakoodin lisäämistä.
|
||||
</string>
|
||||
<string name="invalid_country_code">Virheellinen maakoodi \'%1$s\'</string>
|
||||
@ -334,9 +336,9 @@
|
||||
<string name="battery_optimizations_help">Ota akun käytön optimointi pois päältä,
|
||||
jos haluat vähentää todennäköisyyttä, että Android rajoittaa baresip-sovelluksen toimintaa
|
||||
ja pääsyä verkkoon.</string>
|
||||
<string name="default_phone_app">Oletuspuhelinsovellus</string>
|
||||
<string name="dialer_role_not_available">Puhelinrooli ei ole saatavana</string>
|
||||
<string name="default_phone_app_help">Jos merkity, baresip on oletuspuhelinsovellus. Älä merkitse, jos laitteesi täytyy hallita myös muita kuin SIP-puheluita tai -viestejä.</string>
|
||||
<string name="default_phone_app">Oletus puhelusovellus</string>
|
||||
<string name="dialer_role_not_available">Oletus puhelusovellusrooli ei ole saatavana</string>
|
||||
<string name="default_phone_app_help">Jos merkitty, baresip on oletus puhelusovellus.</string>
|
||||
<string name="listen_address">Kuunteluosoite</string>
|
||||
<string name="listen_address_help">IP-osoite ja portti muotoa
|
||||
\'osoite:portti\', missä baresip kuuntelee sisään tulevia
|
||||
@ -435,9 +437,6 @@
|
||||
<string name="proximity_sensing">Läheisyyden tunnistus</string>
|
||||
<string name="proximity_sensing_help">Jos merkitty, läheisyyden tunnistus on aktiivinen
|
||||
puhelun aikana.</string>
|
||||
<string name="telecom">Telecom-kehys</string>
|
||||
<string name="telecom_help">Käytä Android Telecom-kehystä puheluihin. Jos sinulla on audioon
|
||||
liittyviä ongelmia, kokeile auttaako, kun poistat Telecom-kehyksen käytöstä.</string>
|
||||
<string name="video_size">Videon kehyskoko</string>
|
||||
<string name="video_size_help">Lähetettävän videon kehyskoko (leveys x korkeus)</string>
|
||||
<string name="video_fps">Videokehysten lähetystaajuus</string>
|
||||
@ -530,6 +529,7 @@
|
||||
<string name="call_is_ringing">Puhelu soi</string>
|
||||
<string name="call_is_on_hold">Puhelu on pidossa</string>
|
||||
<string name="call_is_connected">Puhelu on yhdistetty</string>
|
||||
<string name="not_available">Ei saatavilla</string>
|
||||
<string name="rec_in_call">Tallennus voidaan asettaa päälle tai pois vain silloin, kun puhelu
|
||||
ei ole yhdistetty</string>
|
||||
<string name="call_transfer">Puhelun siirto</string>
|
||||
|
||||
@ -113,6 +113,7 @@
|
||||
</string>
|
||||
<!-- Account Activity -->
|
||||
<string name="account">Account</string>
|
||||
<string name="not_available">Not available</string>
|
||||
<string name="account_nickname_help">Nickname (if any) used to identify this account within
|
||||
baresip app.</string>
|
||||
<string name="nickname">Nickname</string>
|
||||
@ -209,11 +210,12 @@
|
||||
<string name="voicemain_uri_help">SIP URI for checking of voicemail messages. If left empty, voicemail
|
||||
messages (Message Waiting Indications) are not subscribed to.
|
||||
</string>
|
||||
<string name="voicemain_tel_uri_help">TEL URI for checking of voicemail messages.</string>
|
||||
<string name="country_code">Country Code</string>
|
||||
<string name="country_code_help">E.164 country code of this account. If From URI userpart of
|
||||
incoming call or message contains a telephone number that does not start with \'+\' sign and if contact
|
||||
<string name="country_code_help">E.164 country code of this account. If peer URI userpart of
|
||||
call or message contains a telephone number that does not start with \'+\' sign and if contact
|
||||
lookup fails, the number is prefixed with this country code and contact lookup is
|
||||
tried again. If the telephone number starts with a single digit \'0\', digit \'0\' is removed
|
||||
tried again. If the number starts with one or two \'0\' digits, they are removed
|
||||
before the number is prefixed.
|
||||
</string>
|
||||
<string name="invalid_country_code">Invalid Country Code \'%1$s\'</string>
|
||||
@ -320,9 +322,8 @@
|
||||
to reduce likelihood that Android restricts baresip\'s access to network or enters baresip
|
||||
to standby state.</string>
|
||||
<string name="default_phone_app">Default Phone App</string>
|
||||
<string name="dialer_role_not_available">Dialer role is not available</string>
|
||||
<string name="default_phone_app_help">If checked, baresip is the default phone app. Do not check
|
||||
if your device may need to handle also other than SIP calls or messages.</string>
|
||||
<string name="dialer_role_not_available">Default phone app role is not available</string>
|
||||
<string name="default_phone_app_help">If checked, baresip is the default phone app.</string>
|
||||
<string name="listen_address">Listen Address</string>
|
||||
<string name="listen_address_help">IP address and port of form \'address:port\' at which baresip listens
|
||||
for incoming SIP requests. If IP address is an IPv6 address, it must be written inside
|
||||
@ -408,9 +409,6 @@
|
||||
<string name="colorblind_help">Use colorblind friendly registration status icons</string>
|
||||
<string name="proximity_sensing">Proximity Sensing</string>
|
||||
<string name="proximity_sensing_help">If checked, proximity sensing is active during calls.</string>
|
||||
<string name="telecom">Telecom Framework</string>
|
||||
<string name="telecom_help">Use Android Telecom framework for calls. If you experience audio
|
||||
related issues, try if it helps when you turn Telecom Framework off.</string>
|
||||
<string name="video_size">Video Frame Size</string>
|
||||
<string name="video_size_help">Size of transmitted video frames (width x height)</string>
|
||||
<string name="video_fps">Video Frames Per Second</string>
|
||||
@ -480,6 +478,7 @@
|
||||
<string name="accept">Accept</string>
|
||||
<string name="deny">Deny</string>
|
||||
<string name="sip_uri" translatable="false">SIP URI</string>
|
||||
<string name="tel_uri" translatable="false">TEL URI</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="edit">Edit</string>
|
||||
|
||||
Reference in New Issue
Block a user