Resolved merge conflict in BaresipService.kt

This commit is contained in:
Juha Heinanen
2026-05-12 11:56:26 +03:00
14 changed files with 961 additions and 700 deletions

View File

@ -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? {

View File

@ -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()

View File

@ -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())

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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>