Improved showing of contact suggestions

This commit is contained in:
Juha Heinanen
2026-08-14 15:52:51 +03:00
parent fd959082ce
commit 1232aebec0
5 changed files with 107 additions and 62 deletions

View File

@ -1083,13 +1083,13 @@ class BaresipService: Service() {
val toastMsg = if (Call.isAnyCallActive(this))
String.format(
getString(R.string.call_auto_rejected),
Utils.friendlyUri(this, peerUri, ua.account)
Utils.friendlyUri(this, peerUri, ua.account, unique = true)
)
else if (ua.account.blockUnknown && Contact.contactName(peerUri) == peerUri) {
blockedCall = true
String.format(
getString(R.string.call_blocked),
Utils.friendlyUri(this, peerUri, ua.account)
Utils.friendlyUri(this, peerUri, ua.account, unique = true)
)
}
else if (ua.account.blockHidden && peerUri.contains("anonymous")) {
@ -1100,7 +1100,7 @@ class BaresipService: Service() {
blockedCall = true
String.format(
getString(R.string.call_blocked),
Utils.friendlyUri(this, peerUri, ua.account)
Utils.friendlyUri(this, peerUri, ua.account, unique = true)
)
}
else if (!Utils.checkPermissions(this, arrayOf(RECORD_AUDIO)))
@ -1286,7 +1286,7 @@ class BaresipService: Service() {
piFlags
)
val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
val target = Utils.friendlyUri(this, ev[1], ua.account)
val target = Utils.friendlyUri(this, ev[1], ua.account, unique = true)
nb.setSmallIcon(R.drawable.ic_notification_call)
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
.setContentIntent(pi)
@ -1509,7 +1509,7 @@ class BaresipService: Service() {
else
String.format(
getString(R.string.message_blocked),
Utils.friendlyUri(this, peerUri, ua.account)
Utils.friendlyUri(this, peerUri, ua.account, unique = true)
)
)
return
@ -1553,7 +1553,7 @@ class BaresipService: Service() {
else
String.format(
getString(R.string.message_blocked),
Utils.friendlyUri(this, peerUri, ua.account)
Utils.friendlyUri(this, peerUri, ua.account, unique = true)
)
)
Blocked(
@ -2003,7 +2003,7 @@ class BaresipService: Service() {
if (activeCall != null) {
val peerUri = activeCall.peerUri
val caller = Utils.friendlyUri(this, peerUri, activeCall.ua.account)
val caller = Utils.friendlyUri(this, peerUri, activeCall.ua.account, unique = true)
val person = Person.Builder().setName(caller).build()
val hangupIntent = Intent(this, BaresipService::class.java)
@ -2176,7 +2176,7 @@ class BaresipService: Service() {
telecomCall.disconnect()
toast(
String.format(getString(R.string.call_auto_rejected),
Utils.friendlyUri(this, uri, ua.account))
Utils.friendlyUri(this, uri, ua.account, unique = true))
)
if (ua.account.callHistory) {
CallHistoryNew(ua.account.aor, uri, "in").add()
@ -2206,7 +2206,7 @@ class BaresipService: Service() {
telecomCall.disconnect()
toast(
String.format(getString(R.string.call_blocked),
Utils.friendlyUri(this, uri, ua.account))
Utils.friendlyUri(this, uri, ua.account, unique = true))
)
if (ua.account.callHistory)
Blocked(
@ -2222,7 +2222,7 @@ class BaresipService: Service() {
telecomCall.disconnect()
toast(
String.format(getString(R.string.call_blocked),
Utils.friendlyUri(this, uri, ua.account))
Utils.friendlyUri(this, uri, ua.account, unique = true))
)
if (ua.account.callHistory)
Blocked(
@ -2347,6 +2347,7 @@ class BaresipService: Service() {
else
telephonyManager.isVoiceCapable
val voiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId()
mobileNumber = Utils.getLine1Number(this, voiceSubId) ?: ""
val existingMobileUa = uas.value.find { it.account.isMobile || Utils.uriMatch(it.account.aor, "sip:mobile@pstn") }
if (mobileAccountHandle == null || !isSimReady || !isVoiceCapable ||
@ -3229,6 +3230,7 @@ class BaresipService: Service() {
var isServiceRunning = false
var isNativeReady = false
var mobileAccount = false
var mobileNumber = ""
var isStartReceived = false
var isConfigInitialized = false
var libraryLoaded = false

View File

@ -540,7 +540,7 @@ private fun NewChatPeer(navController: NavController, account: Account) {
.fillMaxWidth()
.clickable {
val uri = matchingUri?.uri ?: contact.uris().firstOrNull()?.uri ?: contact.name()
newPeer = Utils.friendlyUri(uri, account)
newPeer = Utils.friendlyUri(uri, account, unique = true)
showSuggestions = false
}
.padding(12.dp)
@ -553,14 +553,21 @@ private fun NewChatPeer(navController: NavController, account: Account) {
)
if (matchingUri != null) {
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)
val annotatedUri = if (matchingUri.uri.startsWith("sip:")) {
val userPart = Utils.uriUserPart(matchingUri.uri)
val restPart = uriPart.substring(userPart.length)
buildAnnotatedString {
append(Utils.buildAnnotatedStringWithHighlight(userPart, newPeer))
append(restPart)
}
} else {
val highlightPart = newPeer.filter { c -> c.isDigit() || c == '+' }
Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
}
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
if (matchingUri.label.isNotEmpty() &&
!listOf("SIP", "TEL").contains(matchingUri.label.uppercase()))
append("${matchingUri.label} ")
append(annotatedUri)
},
@ -587,12 +594,13 @@ private fun NewChatPeer(navController: NavController, account: Account) {
else {
val normalizedInput = Utils.unaccent(input)
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
val currentAor = account.aor
BaresipService.contacts.flatMap { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
val uris = contact.uris()
val uris = contact.uris().filter { !Utils.uriMatch(it.uri, currentAor) }
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))
(u.uri.startsWith("sip:") && Utils.uriUserPart(u.uri).contains(normalizedInput, ignoreCase = true))
}
if (nameMatch) {
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)

View File

@ -63,13 +63,16 @@ sealed class Contact {
companion object {
// Return contact name of uri or uri itself if contact with uri is not found
fun contactName(uri: String, includeLabel: Boolean = false): String {
fun contactName(uri: String, includeLabel: Boolean = false, unique: Boolean = false): String {
val contactWithUri = findContactWithUri(uri)
if (contactWithUri != null) {
val name = contactWithUri.first.name()
if (includeLabel) {
val label = contactWithUri.second.label
return if (label.isNotEmpty()) "$name $label" else name
if (label.isNotEmpty()) return "$name $label"
if (unique && contactWithUri.first.uris().size > 1)
return "$name ${uri.substringAfter(":")}"
return name
}
return name
}
@ -80,7 +83,10 @@ sealed class Contact {
val name = telContactWithUri.first.name()
if (includeLabel) {
val label = telContactWithUri.second.label
return if (label.isNotEmpty()) "$name $label" else name
if (label.isNotEmpty()) return "$name $label"
if (unique && telContactWithUri.first.uris().size > 1)
return "$name $userPart"
return name
}
return name
}
@ -138,6 +144,9 @@ sealed class Contact {
val nameWithLabel = if (label.isNotEmpty()) "${c.name()} $label" else c.name()
if (nameWithLabel.equals(name, ignoreCase = true))
return listOf(u)
val nameWithUri = "${c.name()} ${u.uri.substringAfter(":")}"
if (nameWithUri.equals(name, ignoreCase = true))
return listOf(u)
}
}
}

View File

@ -1218,12 +1218,13 @@ private fun CallUriRow(
if (input.length > 1) {
val normalizedInput = Utils.unaccent(input)
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
val currentAor = viewModel.selectedAor.value
filteredSuggestions = BaresipService.contacts.flatMap { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
val uris = contact.uris()
val uris = contact.uris().filter { !Utils.uriMatch(it.uri, currentAor) }
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))
(u.uri.startsWith("sip:") && Utils.uriUserPart(u.uri).contains(normalizedInput, ignoreCase = true))
}
if (nameMatch) {
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
@ -1342,7 +1343,7 @@ private fun CallUriRow(
val aor = viewModel.selectedAor.value
val account = Account.ofAor(aor)
if (account != null) {
dialerState.callUri.value = Utils.friendlyUri(ctx, uri, account)
dialerState.callUri.value = Utils.friendlyUri(ctx, uri, account, unique = true)
dialerState.redialUri = uri
} else {
dialerState.callUri.value = uri.substringAfter(":")
@ -1359,14 +1360,21 @@ private fun CallUriRow(
)
if (matchingUri != null) {
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)
val annotatedUri = if (matchingUri.uri.startsWith("sip:")) {
val userPart = Utils.uriUserPart(matchingUri.uri)
val restPart = uriPart.substring(userPart.length)
buildAnnotatedString {
append(Utils.buildAnnotatedStringWithHighlight(userPart, dialerState.callUri.value))
append(restPart)
}
} else {
val highlightPart = dialerState.callUri.value.filter { c -> c.isDigit() || c == '+' }
Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
}
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
if (matchingUri.label.isNotEmpty() &&
!listOf("SIP", "TEL").contains(matchingUri.label.uppercase()))
append("${matchingUri.label} ")
append(annotatedUri)
},
@ -1682,12 +1690,13 @@ private fun CallRow(
if (input.length > 1) {
val normalizedInput = Utils.unaccent(input)
val numericInput = input.filter { c -> c.isDigit() || c == '+' }
val currentAor = call.ua.account.aor
filteredSuggestions = BaresipService.contacts.flatMap { contact ->
val nameMatch = Utils.unaccent(contact.name()).contains(normalizedInput, ignoreCase = true)
val uris = contact.uris()
val uris = contact.uris().filter { !Utils.uriMatch(it.uri, currentAor) }
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))
(u.uri.startsWith("sip:") && Utils.uriUserPart(u.uri).contains(normalizedInput, ignoreCase = true))
}
if (nameMatch) {
val annotatedName = Utils.buildAnnotatedStringWithHighlight(contact.name(), input)
@ -1759,7 +1768,7 @@ private fun CallRow(
.fillMaxWidth()
.clickable {
val uri = matchingUri?.uri ?: contact.uris().firstOrNull()?.uri ?: contact.name()
transferUri = Utils.friendlyUri(ctx, uri, call.ua.account)
transferUri = Utils.friendlyUri(ctx, uri, call.ua.account, unique = true)
call.showSuggestions.value = false
}
.padding(12.dp)
@ -1772,14 +1781,21 @@ private fun CallRow(
)
if (matchingUri != null) {
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)
val annotatedUri = if (matchingUri.uri.startsWith("sip:")) {
val userPart = Utils.uriUserPart(matchingUri.uri)
val restPart = uriPart.substring(userPart.length)
buildAnnotatedString {
append(Utils.buildAnnotatedStringWithHighlight(userPart, transferUri))
append(restPart)
}
} else {
val highlightPart = transferUri.filter { c -> c.isDigit() || c == '+' }
Utils.buildAnnotatedStringWithHighlight(uriPart, highlightPart)
}
Text(
text = buildAnnotatedString {
if (matchingUri.label.isNotEmpty())
if (matchingUri.label.isNotEmpty() &&
!listOf("SIP", "TEL").contains(matchingUri.label.uppercase()))
append("${matchingUri.label} ")
append(annotatedUri)
},
@ -2053,7 +2069,7 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
val aor = viewModel.selectedAor.value
val ua = UserAgent.ofAor(aor)
val uriToCall = if (dialerState.redialUri != "" &&
uriText == Utils.friendlyUri(ctx, dialerState.redialUri, ua!!.account))
uriText == Utils.friendlyUri(ctx, dialerState.redialUri, ua!!.account, unique = true))
dialerState.redialUri
else {
val uris = Contact.contactContactUris(uriText, ua?.account?.isMobile ?: false)
@ -2092,7 +2108,7 @@ private fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel
val latestPeerUri = CallHistoryNew.aorLatestPeerUri(ua.account.aor)
if (latestPeerUri != null) {
dialerState.redialUri = latestPeerUri
dialerState.callUri.value = Utils.friendlyUri(ctx, latestPeerUri, ua.account)
dialerState.callUri.value = Utils.friendlyUri(ctx, latestPeerUri, ua.account, unique = true)
}
}
}
@ -2318,7 +2334,7 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
ctx.getString(R.string.incoming_call_from_dots)
else
ctx.getString(R.string.outgoing_call_to_dots)
call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account, unique = true)
call.callUri2.value = ""
call.showCallTimer.value = false
call.securityIconTint.value = -1
@ -2333,11 +2349,11 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
call.showCallTimer.value = false
call.securityIconTint.value = -1
call.callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots)
call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account, unique = true)
val uri = call.diverterUri()
if (uri != "") {
call.callUriLabel2.value = ctx.getString(R.string.diverted_by_dots)
call.callUri2.value = Utils.friendlyUri(ctx, uri, ua.account)
call.callUri2.value = Utils.friendlyUri(ctx, uri, ua.account, unique = true)
}
else
call.callUri2.value = ""
@ -2351,7 +2367,7 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
"connected" -> {
if (call.referTo != "") {
call.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
call.callUri.value = Utils.friendlyUri(ctx, call.referTo, ua.account)
call.callUri.value = Utils.friendlyUri(ctx, call.referTo, ua.account, unique = true)
call.transferButtonEnabled.value = false
}
else {
@ -2359,7 +2375,7 @@ private fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCal
call.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)
else
call.callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots)
call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)
call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account, unique = true)
call.transferButtonEnabled.value = !ua.account.isMobile
}
call.callUri2.value = ""
@ -2671,7 +2687,7 @@ fun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: Str
}
viewModel.navigateToHome()
val peer = intent.getStringExtra("peer")!!
viewModel.dialerState.callUri.value = Utils.friendlyUri(ctx, peer, ua.account)
viewModel.dialerState.callUri.value = Utils.friendlyUri(ctx, peer, ua.account, unique = true)
viewModel.dialerState.redialUri = peer
spinToAor(viewModel, ua.account.aor)
if (ev[0] == "call") {

View File

@ -152,14 +152,24 @@ object Utils {
}
fun uriMatch(firstUri: String, secondUri: String): Boolean {
val first = firstUri.removePrefix("<").removeSuffix(">")
val second = secondUri.removePrefix("<").removeSuffix(">")
if (first.startsWith("tel:", ignoreCase = true))
return first.equals(second, ignoreCase = true) ||
first.substringAfter(":").equals(uriUserPart(second), ignoreCase = true)
if (first.startsWith("sip:", ignoreCase = true))
return uriUserPart(first).equals(uriUserPart(second), ignoreCase = true) &&
uriHostPart(first).equals(uriHostPart(second), ignoreCase = true)
val first = uriUnescape(firstUri.removePrefix("<").removeSuffix(">"))
val second = uriUnescape(secondUri.removePrefix("<").removeSuffix(">"))
val mobileAor = "sip:mobile@pstn"
if (first.startsWith("tel:", ignoreCase = true)) {
if (first.equals(second, ignoreCase = true)) return true
val firstUser = uriUserPart(first)
if (second == mobileAor && BaresipService.mobileNumber != "")
return firstUser == BaresipService.mobileNumber || firstUser == uriUserPart(BaresipService.mobileNumber)
return firstUser.equals(uriUserPart(second), ignoreCase = true)
}
if (first.startsWith("sip:", ignoreCase = true)) {
val firstUser = uriUserPart(first)
val firstHost = uriHostPart(first)
if (second == mobileAor && BaresipService.mobileNumber != "")
return firstUser == BaresipService.mobileNumber || firstUser == uriUserPart(BaresipService.mobileNumber)
return firstUser.equals(uriUserPart(second), ignoreCase = true) &&
firstHost.equals(uriHostPart(second), ignoreCase = true)
}
return false
}
@ -169,20 +179,20 @@ object Utils {
}
fun friendlyUri(ctx: Context, uri: String, account: Account, e164Check: Boolean = true,
includeLabel: Boolean = true): String {
return friendlyUri(uri, account, e164Check, includeLabel,
includeLabel: Boolean = true, unique: Boolean = false): String {
return friendlyUri(uri, account, e164Check, includeLabel, unique,
ctx.getString(R.string.anonymous), ctx.getString(R.string.unknown))
}
fun friendlyUri(uri: String, account: Account, e164Check: Boolean = true,
includeLabel: Boolean = true, anonymous: String = "Anonymous",
unknown: String = "Unknown"): String {
var u = Contact.contactName(uri, includeLabel)
includeLabel: Boolean = true, unique: Boolean = false,
anonymous: String = "Anonymous", unknown: String = "Unknown"): String {
var u = Contact.contactName(uri, includeLabel, unique)
if (u != uri)
return u
if (e164Check) {
val e164Uri = e164Uri(uri, account.countryCode)
u = Contact.contactName(e164Uri, includeLabel)
u = Contact.contactName(e164Uri, includeLabel, unique)
if (u != e164Uri)
return u
}