Mobile account and calling related fixes and enhancements

Search and show also contact URIs in suggestions
This commit is contained in:
Juha Heinanen
2026-08-14 09:14:33 +03:00
parent a8edd0f234
commit 3af45d8efd
6 changed files with 171 additions and 113 deletions

View File

@ -892,7 +892,8 @@ class BaresipService: Service() {
Blocked.clear(ua.account.aor)
BlockRule.clear(ua.account.aor)
Api.ua_destroy(uap)
if (isNativeReady) Account.saveAccounts()
ua.remove()
Account.saveAccounts()
return
}
@ -903,10 +904,11 @@ class BaresipService: Service() {
R.drawable.circle_white
else
circleRed.getValue(colorblind)
} else {
R.drawable.circle_white
}
} else if (ua.account.regint == 0)
else
R.drawable.circle_white
}
else if (ua.account.regint == 0)
R.drawable.circle_white
else
circleYellow.getValue(colorblind)
@ -1076,7 +1078,7 @@ class BaresipService: Service() {
"incoming call" -> {
if (speakerPhoneAuto)
speakerPhone = true
val peerUri = ev[1]
val peerUri = Utils.uriUnescape(ev[1])
var blockedCall = false
val toastMsg = if (Call.isAnyCallActive(this))
String.format(
@ -1143,7 +1145,7 @@ class BaresipService: Service() {
}
"call incoming" -> {
val peerUri = ev[1]
val peerUri = Utils.uriUnescape(ev[1])
Log.d(TAG, "Incoming call $uap/$callp/$peerUri")
if (Call.ofCallp(callp) == null)
Call(callp, ua, peerUri, "in", "incoming").add()
@ -1714,7 +1716,9 @@ class BaresipService: Service() {
private var audioModeChangedListener: AudioManager.OnModeChangedListener? = null
fun runCall(uap: Long, uri: String, conferenceCall: Boolean, onHoldCallp: Long) {
fun runCall(uap: Long, uriText: String, conferenceCall: Boolean, onHoldCallp: Long) {
val uri = Utils.uriUnescape(uriText)
val ua = UserAgent.ofUap(uap)
if (ua != null && ua.account.isMobile) {
@ -3047,13 +3051,27 @@ class BaresipService: Service() {
}
private fun registerPhoneAccount() {
val phoneAccountHandle = getPhoneAccountHandle(this)
val phoneAccount = android.telecom.PhoneAccount.builder(phoneAccountHandle, getString(R.string.app_name))
.setCapabilities(android.telecom.PhoneAccount.CAPABILITY_SELF_MANAGED)
val sipHandle = getPhoneAccountHandle(this, SIP_ACCOUNT_ID)
val pstnHandle = getPhoneAccountHandle(this, PSTN_ACCOUNT_ID)
val sipAccount = android.telecom.PhoneAccount.builder(sipHandle, getString(R.string.app_name))
.setCapabilities(android.telecom.PhoneAccount.CAPABILITY_SELF_MANAGED or
android.telecom.PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.mipmap.ic_launcher))
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_SIP)
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_TEL)
.build()
tm.registerPhoneAccount(phoneAccount)
val pstnAccount = android.telecom.PhoneAccount.builder(pstnHandle, getString(R.string.app_name) + " Mobile")
.setCapabilities(android.telecom.PhoneAccount.CAPABILITY_CALL_PROVIDER or
android.telecom.PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.mipmap.ic_launcher))
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_SIP)
.addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_TEL)
.build()
tm.registerPhoneAccount(sipAccount)
tm.registerPhoneAccount(pstnAccount)
}
fun isSimReady(): Boolean {
@ -3197,7 +3215,7 @@ class BaresipService: Service() {
var instance: BaresipService? = null
var isServiceRunning = false
var isNativeReady = false
var mobileAccount = true
var mobileAccount = false
var isStartReceived = false
var isConfigInitialized = false
var libraryLoaded = false
@ -3295,11 +3313,12 @@ class BaresipService: Service() {
)
internal const val KEY_TEXT_REPLY = "key_text_reply_baresip"
private const val PHONE_ACCOUNT_ID = "baresip_phone_account"
const val SIP_ACCOUNT_ID = "baresip_sip_account"
const val PSTN_ACCOUNT_ID = "baresip_phone_account"
fun getPhoneAccountHandle(ctx: Context): PhoneAccountHandle {
fun getPhoneAccountHandle(ctx: Context, id: String = SIP_ACCOUNT_ID): PhoneAccountHandle {
val componentName = android.content.ComponentName(ctx, ConnectionService::class.java)
return PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID)
return PhoneAccountHandle(componentName, id)
}
fun setMicMute(mute: Boolean) {

View File

@ -460,7 +460,8 @@ private fun NewChatPeer(navController: NavController, account: Account) {
String.format(noTelephonyProviderText, account.aor)
showAlert.value = true
""
} else
}
else
Utils.telToSip(peerUri, account)
}
else
@ -532,7 +533,7 @@ private fun NewChatPeer(navController: NavController, account: Account) {
) {
items(
items = filteredSuggestions,
key = { (contact, _, _) -> "${contact.id()}" }
key = { (contact, _, matchingUri) -> "${contact.id()}:${matchingUri?.uri ?: ""}" }
) { (contact, annotatedName, matchingUri) ->
Box(
modifier = Modifier
@ -551,15 +552,17 @@ private fun NewChatPeer(navController: NavController, account: Account) {
fontSize = 18.sp
)
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
newPeer.filter { c -> c.isDigit() || c == '+' })
val uriPart = matchingUri.uri.substringAfter(":")
val highlightPart = if (matchingUri.uri.startsWith("tel:"))
newPeer.filter { c -> c.isDigit() || c == '+' }
else
newPeer
val annotatedUri = Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
append(annotatedUri)
},
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
@ -576,31 +579,32 @@ private fun NewChatPeer(navController: NavController, account: Account) {
OutlinedTextField(
value = newPeer,
placeholder = { Text(stringResource(R.string.new_chat_peer)) },
onValueChange = {
newPeer = it
onValueChange = { input ->
newPeer = input
showSuggestions = newPeer.length > 1
filteredSuggestions = if (it.isEmpty())
filteredSuggestions = if (input.length <= 1)
emptyList()
else {
val normalizedInput = Utils.unaccent(it)
val numericInput = it.filter { c -> c.isDigit() || c == '+' }
BaresipService.contacts.mapNotNull { contact ->
val normalizedInput = Utils.unaccent(input)
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
BaresipService.contacts.flatMap { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
var matchingUri: Contact.ContactUri? = null
if (numericInput.isNotEmpty()) {
matchingUri = contact.uris().find { u ->
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
}
val uris = contact.uris()
val matchingUris = uris.filter { u ->
(u.uri.startsWith("tel:") && numericInput.isNotEmpty() && u.uri.substring(4).contains(numericInput)) ||
(u.uri.startsWith("sip:") && u.uri.substring(4).contains(normalizedInput, ignoreCase = true))
}
if (nameMatch || matchingUri != null) {
val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
if (nameMatch) {
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
if (uris.isEmpty())
listOf(Triple(contact, annotatedName, null))
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
uris.map { Triple(contact, annotatedName, it) }
}
else if (matchingUris.isNotEmpty())
matchingUris.map { Triple(contact, AnnotatedString(contact.name()), it) }
else
emptyList()
}
}
},
@ -643,9 +647,9 @@ private fun NewChatPeer(navController: NavController, account: Account) {
else
String.format(contactNoSipOrTelUriText, peerText)
showAlert.value = true
} else {
makeChat(navController, account, peerText)
}
else
makeChat(navController, account, peerText)
}
else if (uris.size == 1)
makeChat(navController, account, uris[0].uri)

View File

@ -44,11 +44,10 @@ class ConnectionService : ConnectionService() {
@Suppress("DEPRECATION")
val currentRoute = it.callAudioState?.route ?: CallAudioState.ROUTE_EARPIECE
@Suppress("DEPRECATION")
if (speaker) {
if (speaker)
it.setAudioRoute(CallAudioState.ROUTE_SPEAKER)
} else if (currentRoute == CallAudioState.ROUTE_SPEAKER) {
else if (currentRoute == CallAudioState.ROUTE_SPEAKER)
it.setAudioRoute(CallAudioState.ROUTE_EARPIECE)
}
}
}
}
@ -72,6 +71,8 @@ class ConnectionService : ConnectionService() {
Connection.CAPABILITY_HOLD or
Connection.CAPABILITY_MERGE_CONFERENCE or
Connection.CAPABILITY_SWAP_CONFERENCE
if (request?.accountHandle?.id == BaresipService.SIP_ACCOUNT_ID)
connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
connection.audioModeIsVoip = true
val call = Call.ofCallp(callp)
@ -110,9 +111,14 @@ class ConnectionService : ConnectionService() {
val rootExtras = request?.extras
val nestedExtras = rootExtras?.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)
val uap = rootExtras?.getLong("uap", 0L).takeIf { it != 0L }
var uap = rootExtras?.getLong("uap", 0L).takeIf { it != 0L }
?: nestedExtras?.getLong("uap") ?: 0L
if (uap == 0L && BaresipService.uas.value.isNotEmpty()) {
uap = BaresipService.uas.value[0].uap
Log.d(TAG, "Outgoing connection request from system, using default uap $uap")
}
val conferenceCall = rootExtras?.getBoolean("conferenceCall", false) ?:
nestedExtras?.getBoolean("conferenceCall") ?: false
@ -122,7 +128,7 @@ class ConnectionService : ConnectionService() {
val onHoldCallp = rootExtras?.getLong("onHoldCallp", 0L).takeIf { it != 0L }
?: nestedExtras?.getLong("onHoldCallp") ?: 0L
val destination = request?.address?.encodedSchemeSpecificPart ?: ""
val destination = request?.address?.schemeSpecificPart ?: ""
Log.d(TAG, "onCreateOutgoingConnection to $destination (uap=$uap)")
@ -143,6 +149,8 @@ class ConnectionService : ConnectionService() {
Connection.CAPABILITY_HOLD or
Connection.CAPABILITY_MERGE_CONFERENCE or
Connection.CAPABILITY_SWAP_CONFERENCE
if (request?.accountHandle?.id == BaresipService.SIP_ACCOUNT_ID)
connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
if (!pstnCall) {
connection.audioModeIsVoip = true
@ -225,9 +233,9 @@ class ConnectionService : ConnectionService() {
if (callp != 0L) {
Api.ua_hangup(uap, callp, 0, "")
connections.remove(callp)
} else {
pendingOutgoingConnection = null
}
else
pendingOutgoingConnection = null
setDisconnected(DisconnectCause(DisconnectCause.CANCELED))
destroy()
}
@ -281,9 +289,9 @@ class ConnectionService : ConnectionService() {
call.showOnHoldNotice.value = true
// 3. Tell Telecom the move is complete
setOnHold()
} else {
Log.e(TAG, "SIP Hold failed for $callp")
}
else
Log.e(TAG, "SIP Hold failed for $callp")
}
}
@ -299,9 +307,9 @@ class ConnectionService : ConnectionService() {
call.showOnHoldNotice.value = false
// 3. Tell Telecom we are active
setActive()
} else {
Log.e(TAG, "SIP Resume failed for $callp")
}
else
Log.e(TAG, "SIP Resume failed for $callp")
}
}

View File

@ -18,9 +18,10 @@ class InCallService : InCallService() {
Log.d(TAG, "InCallService: Call added")
val handle = call.details.accountHandle
val baresipHandle = BaresipService.getPhoneAccountHandle(this)
val baresipSipHandle = BaresipService.getPhoneAccountHandle(this, BaresipService.SIP_ACCOUNT_ID)
val baresipPstnHandle = BaresipService.getPhoneAccountHandle(this, BaresipService.PSTN_ACCOUNT_ID)
if (handle == baresipHandle) {
if (handle == baresipSipHandle || handle == baresipPstnHandle) {
Log.d(TAG, "InCallService: Identified as SIP call")
// SIP call is already managed by ConnectionService/BaresipService
}

View File

@ -169,6 +169,8 @@ import com.tutpro.baresip.CustomElements.PasswordDialog
import com.tutpro.baresip.CustomElements.SelectableAlertDialog
import com.tutpro.baresip.CustomElements.verticalScrollbar
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.lifecycle.viewModelScope
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
@ -1204,38 +1206,39 @@ private fun CallUriRow(
value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,
readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,
singleLine = true,
onValueChange = {
onValueChange = { input ->
if (isDialer)
if (it != dialerState.callUri.value) {
dialerState.callUri.value = it
if (input != dialerState.callUri.value) {
dialerState.callUri.value = input
dialerState.redialUri = ""
if (it == "") {
if (input == "") {
dialerState.showCallButton.value = true
dialerState.showCallConferenceButton.value = true
}
if (it.length > 1) {
val normalizedInput = Utils.unaccent(it)
val numericInput = it.filter { c -> c.isDigit() || c == '+' }
filteredSuggestions = BaresipService.contacts.mapNotNull { contact ->
if (input.length > 1) {
val normalizedInput = Utils.unaccent(input)
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
filteredSuggestions = BaresipService.contacts.flatMap { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
var matchingUri: Contact.ContactUri? = null
if (numericInput.isNotEmpty()) {
matchingUri = contact.uris().find { u ->
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
}
val uris = contact.uris()
val matchingUris = uris.filter { u ->
(u.uri.startsWith("tel:") && numericInput.isNotEmpty() && u.uri.substring(4).contains(numericInput)) ||
(u.uri.startsWith("sip:") && u.uri.substring(4).contains(normalizedInput, ignoreCase = true))
}
if (nameMatch || matchingUri != null) {
val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
if (nameMatch) {
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
if (uris.isEmpty())
listOf(Triple(contact, annotatedName, null))
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
uris.map { Triple(contact, annotatedName, it) }
}
else if (matchingUris.isNotEmpty())
matchingUris.map { Triple(contact, AnnotatedString(contact.name()), it) }
else
emptyList()
}
}
dialerState.showSuggestions.value = it.length > 1
dialerState.showSuggestions.value = input.length > 1
}
},
trailingIcon = {
@ -1329,7 +1332,7 @@ private fun CallUriRow(
) {
items(
items = filteredSuggestions,
key = { (contact, _, _) -> "${contact.id()}" }
key = { (contact, _, matchingUri) -> "${contact.id()}:${matchingUri?.uri ?: ""}" }
) { (contact, annotatedName, matchingUri) ->
Box(
modifier = Modifier
@ -1348,15 +1351,17 @@ private fun CallUriRow(
fontSize = 18.sp
)
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
dialerState.callUri.value.filter { c -> c.isDigit() || c == '+' })
val uriPart = matchingUri.uri.substringAfter(":")
val highlightPart = if (matchingUri.uri.startsWith("tel:"))
dialerState.callUri.value.filter { c -> c.isDigit() || c == '+' }
else
dialerState.callUri.value
val annotatedUri = Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
append(annotatedUri)
},
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
@ -1664,29 +1669,30 @@ private fun CallRow(
OutlinedTextField(
value = transferUri,
singleLine = true,
onValueChange = {
if (it != transferUri) {
transferUri = it
if (it.length > 1) {
val normalizedInput = Utils.unaccent(it)
val numericInput = it.filter { c -> c.isDigit() || c == '+' }
filteredSuggestions = BaresipService.contacts.mapNotNull { contact ->
onValueChange = { input ->
if (input != transferUri) {
transferUri = input
if (input.length > 1) {
val normalizedInput = Utils.unaccent(input)
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
filteredSuggestions = BaresipService.contacts.flatMap { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
var matchingUri: Contact.ContactUri? = null
if (numericInput.isNotEmpty()) {
matchingUri = contact.uris().find { u ->
u.uri.startsWith("tel:") && u.uri.substring(4).contains(numericInput)
}
val uris = contact.uris()
val matchingUris = uris.filter { u ->
(u.uri.startsWith("tel:") && numericInput.isNotEmpty() && u.uri.substring(4).contains(numericInput)) ||
(u.uri.startsWith("sip:") && u.uri.substring(4).contains(normalizedInput, ignoreCase = true))
}
if (nameMatch || matchingUri != null) {
val annotatedName = if (nameMatch)
Utils.buildAnnotatedStringWithHighlight(contact.name(), it)
if (nameMatch) {
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
if (uris.isEmpty())
listOf(Triple(contact, annotatedName, null))
else
AnnotatedString(contact.name())
Triple(contact, annotatedName, matchingUri)
} else {
null
uris.map { Triple(contact, annotatedName, it) }
}
else if (matchingUris.isNotEmpty())
matchingUris.map { Triple(contact, AnnotatedString(contact.name()), it) }
else
emptyList()
}
}
call.showSuggestions.value = transferUri.length > 1
@ -1739,7 +1745,7 @@ private fun CallRow(
) {
items(
items = filteredSuggestions,
key = { (contact, _, _) -> "${contact.id()}" }
key = { (contact, _, matchingUri) -> "${contact.id()}:${matchingUri?.uri ?: ""}" }
) { (contact, annotatedName, matchingUri) ->
Box(
modifier = Modifier
@ -1758,15 +1764,17 @@ private fun CallRow(
fontSize = 18.sp
)
if (matchingUri != null) {
val tel = matchingUri.uri.substring(4)
val annotatedTel = Utils.buildAnnotatedStringWithHighlight(
tel,
transferUri.filter { c -> c.isDigit() || c == '+' })
val uriPart = matchingUri.uri.substringAfter(":")
val highlightPart = if (matchingUri.uri.startsWith("tel:"))
transferUri.filter { c -> c.isDigit() || c == '+' }
else
transferUri
val annotatedUri = Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
append("${matchingUri.label} ")
append(annotatedTel)
append(annotatedUri)
},
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
@ -2150,6 +2158,14 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
try {
Log.i(TAG, "Placing Telecom PSTN call to $uri with uap=${ua.uap}")
val extras = Bundle().apply {
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
BaresipService.getPhoneAccountHandle(ctx, BaresipService.PSTN_ACCOUNT_ID))
}
val callExtras = Bundle()
callExtras.putBoolean("pstnCall", true)
callExtras.putString("aor", aor)
extras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
val telecomUri = if (uri.startsWith("tel:"))
Uri.fromParts("tel", uri.substring(4), null)
else
@ -2166,7 +2182,7 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
val extras = Bundle()
extras.putParcelable(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
BaresipService.getPhoneAccountHandle(ctx)
BaresipService.getPhoneAccountHandle(ctx, BaresipService.SIP_ACCOUNT_ID)
)
val callExtras = Bundle()
callExtras.putBoolean("conferenceCall", dialerState.showCallConferenceButton.value)
@ -2185,6 +2201,14 @@ private fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String,
Log.e(TAG, error)
viewModel.dialerState.callButtonsEnabled.value = true
}
else
viewModel.viewModelScope.launch {
delay(5000.milliseconds)
if (Call.calls().isEmpty()) {
Log.d(TAG, "Re-enabling dialer buttons after timeout (no calls)")
viewModel.dialerState.callButtonsEnabled.value = true
}
}
}
}
@ -2467,7 +2491,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
spinToAor(viewModel, aor, Call.ofCallp(callp))
}
"call redirect" -> {
val redirectUri = ev[1]
val redirectUri = Utils.uriUnescape(ev[1])
val target = Utils.friendlyUri(ctx, redirectUri, acc)
if (acc.autoRedirect) {
redirect(ctx, viewModel, ua, redirectUri)
@ -2545,7 +2569,7 @@ fun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params
viewModel.navigateToHome()
val callp = params[1] as Long
val call = Call.ofCallp(callp)
val target = Utils.friendlyUri(ctx, ev[1], acc)
val target = Utils.friendlyUri(ctx, Utils.uriUnescape(ev[1]), acc)
dialogTitle.value = if (call != null)
ctx.getString(R.string.transfer_request)
else

View File

@ -212,10 +212,12 @@ object Utils {
return u
}
fun e164Uri(uri: String, countryCode: String): String {
fun e164Uri(uriText: String, countryCode: String): String {
val uri = uriUnescape(uriText)
val scheme = uri.take(4)
val userPart = uriUserPart(uri)
return if (userPart.isNotEmpty() && userPart.isDigitsOnly())
val digitsOnlyUserPart = userPart.filter { it.isDigit() }
return if (digitsOnlyUserPart.isNotEmpty() && digitsOnlyUserPart.length == userPart.filterNot { it == ' ' || it == '-' || it == '(' || it == ')' || it == '+' }.length)
when {
userPart.startsWith("00") -> uri.replace("$scheme$userPart",
scheme + "+" + userPart.substring(2))
@ -238,7 +240,7 @@ object Utils {
fun uriUnescape(uri: String): String {
return uri.replace("%2B" to "+", "%3A" to ":", "%3B" to ";", "%40" to "@", "%3D" to "=",
"%23" to "#", "%2A" to "*")
"%23" to "#", "%2A" to "*", "%20" to "")
}
fun aorDomain(aor: String): String {