From 1232aebec070b8545539d78c3f9e11188efb9297 Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Fri, 14 Aug 2026 15:52:51 +0300 Subject: [PATCH] Improved showing of contact suggestions --- .../com/tutpro/baresip/BaresipService.kt | 22 +++--- .../kotlin/com/tutpro/baresip/ChatsScreen.kt | 26 ++++--- .../main/kotlin/com/tutpro/baresip/Contact.kt | 15 +++- .../kotlin/com/tutpro/baresip/MainScreen.kt | 68 ++++++++++++------- .../main/kotlin/com/tutpro/baresip/Utils.kt | 38 +++++++---- 5 files changed, 107 insertions(+), 62 deletions(-) diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index e25b89c3..a7dc2d8e 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -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 diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt index 3e06e908..e25802c2 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt @@ -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) diff --git a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt index 818782ad..f5f64361 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt @@ -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) } } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt index ba6ea443..ac9ed5a7 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt @@ -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") { diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index ab23d607..7da84a4b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -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 }