From 82d6d37fd76c2f4ac4fc7a6ddd8414085a67a915 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 19 Feb 2024 15:55:08 +0100 Subject: [PATCH] Make sure to always search contacts using a cleaned SIP address + improved Android DB search for SIP address + delay loading contacts to after an account was successfully registered --- .../org/linphone/contacts/ContactsManager.kt | 44 +++++++++++-------- .../ui/call/viewmodel/CurrentCallViewModel.kt | 13 +++--- .../java/org/linphone/ui/main/MainActivity.kt | 12 +++-- .../viewmodel/ConversationInfoViewModel.kt | 2 +- .../ui/main/viewmodel/MainViewModel.kt | 43 +++++++++++++----- .../java/org/linphone/utils/LinphoneUtils.kt | 10 +++++ 6 files changed, 83 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/linphone/contacts/ContactsManager.kt b/app/src/main/java/org/linphone/contacts/ContactsManager.kt index 3620869ab..c8b70e3e7 100644 --- a/app/src/main/java/org/linphone/contacts/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contacts/ContactsManager.kt @@ -26,6 +26,7 @@ import android.database.Cursor import android.graphics.Bitmap import android.net.Uri import android.provider.ContactsContract +import androidx.annotation.MainThread import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.core.app.ActivityCompat @@ -120,7 +121,7 @@ class ContactsManager @UiThread constructor() { } } - @UiThread + @MainThread fun loadContacts(activity: MainActivity) { val manager = LoaderManager.getInstance(activity) manager.restartLoader(0, null, ContactLoader()) @@ -236,15 +237,25 @@ class ContactsManager @UiThread constructor() { @WorkerThread fun findContactByAddress(address: Address): Friend? { - val sipUri = address.asStringUriOnly() + val sipUri = LinphoneUtils.getAddressAsCleanStringUriOnly(address) Log.d("$TAG Looking for friend with SIP URI [$sipUri]") val username = address.username val found = coreContext.core.findFriend(address) - return if (found != null) { + if (found != null) { Log.d("$TAG Friend [${found.name}] was found using SIP URI [$sipUri]") - found - } else if (!username.isNullOrEmpty() && username.startsWith("+")) { + return found + } + + val sipAddress = if (sipUri.startsWith("sip:")) { + sipUri.substring("sip:".length) + } else if (sipUri.startsWith("sips:")) { + sipUri.substring("sips:".length) + } else { + sipUri + } + + return if (!username.isNullOrEmpty() && username.startsWith("+")) { Log.d("$TAG Looking for friend with phone number [$username]") val foundUsingPhoneNumber = coreContext.core.findFriendByPhoneNumber(username) if (foundUsingPhoneNumber != null) { @@ -256,13 +267,13 @@ class ContactsManager @UiThread constructor() { Log.d( "$TAG Friend wasn't found using phone number [$username], looking in native address book directly" ) - findNativeContact(sipUri, true, username) + findNativeContact(sipAddress, username, true) } } else { Log.d( - "$TAG Friend wasn't found using SIP URI [$sipUri] and username [$username] isn't a phone number, looking in native address book directly" + "$TAG Friend wasn't found using SIP address [$sipAddress] and username [$username] isn't a phone number, looking in native address book directly" ) - findNativeContact(sipUri, false) + findNativeContact(sipAddress, username.orEmpty(), false) } } @@ -339,10 +350,7 @@ class ContactsManager @UiThread constructor() { "$TAG Looking for avatar model for friend [${friend.name}] using SIP URI [${address.asStringUriOnly()}]" ) - val clone = address.clone() - clone.clean() - val key = clone.asStringUriOnly() - + val key = LinphoneUtils.getAddressAsCleanStringUriOnly(address) val foundInMap = getAvatarModelFromCache(key) if (foundInMap != null) { Log.d("$TAG Found avatar model in map using SIP URI [$key]") @@ -402,7 +410,7 @@ class ContactsManager @UiThread constructor() { } @WorkerThread - fun findNativeContact(address: String, searchAsPhoneNumber: Boolean, number: String = ""): Friend? { + fun findNativeContact(address: String, username: String, searchAsPhoneNumber: Boolean): Friend? { if (nativeContactsLoaded) { Log.d( "$TAG Native contacts already loaded, no need to search further, no native contact matches address [$address]" @@ -417,19 +425,19 @@ class ContactsManager @UiThread constructor() { ) == PackageManager.PERMISSION_GRANTED ) { Log.d( - "$TAG Looking for native contact with address [$address] ${if (searchAsPhoneNumber) "or phone number [$number]" else ""}" + "$TAG Looking for native contact with address [$address] ${if (searchAsPhoneNumber) "or phone number [$username]" else ""}" ) try { val selection = if (searchAsPhoneNumber) { - "${ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER} LIKE ? OR ${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ?" + "${ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER} LIKE ? OR ${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ? OR ${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ? OR ${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ?" } else { - "${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ?" + "${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ? OR ${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ? OR ${ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS} LIKE ?" } val selectionParams = if (searchAsPhoneNumber) { - arrayOf(number, address) + arrayOf(username, address, "sip:$address", username) } else { - arrayOf(address) + arrayOf(address, "sip:$address", username) } val cursor: Cursor? = context.contentResolver.query( ContactsContract.Data.CONTENT_URI, diff --git a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt index afff2e53b..8801b4a33 100644 --- a/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt +++ b/app/src/main/java/org/linphone/ui/call/viewmodel/CurrentCallViewModel.kt @@ -810,25 +810,22 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() { isPausedByRemote.postValue(call.state == Call.State.PausedByRemote) canBePaused.postValue(canCallBePaused()) - val address = call.remoteAddress.clone() - address.clean() + val address = call.remoteAddress displayedAddress.postValue(address.asStringUriOnly()) - val conferenceInfo = coreContext.core.findConferenceInformationFromUri( - call.remoteAddress - ) + val conferenceInfo = coreContext.core.findConferenceInformationFromUri(address) val model = if (conferenceInfo != null) { coreContext.contactsManager.getContactAvatarModelForConferenceInfo(conferenceInfo) } else { // Do not use contact avatar model from ContactsManager - // coreContext.contactsManager.getContactAvatarModelForAddress(call.remoteAddress) - val friend = coreContext.contactsManager.findContactByAddress(call.remoteAddress) + // coreContext.contactsManager.getContactAvatarModelForAddress(address) + val friend = coreContext.contactsManager.findContactByAddress(address) if (friend != null) { ContactAvatarModel(friend) } else { val fakeFriend = coreContext.core.createFriend() fakeFriend.name = LinphoneUtils.getDisplayName(address) - fakeFriend.address = call.remoteAddress + fakeFriend.address = address ContactAvatarModel(fakeFriend) } } diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 740fb1640..e704c1bb6 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -96,10 +96,6 @@ class MainActivity : GenericActivity() { Thread.sleep(50) } - if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { - loadContacts() - } - viewModel = run { ViewModelProvider(this)[MainViewModel::class.java] } @@ -160,6 +156,14 @@ class MainActivity : GenericActivity() { } } + viewModel.startLoadingContactsEvent.observe(this) { + it.consume { + if (checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { + loadContacts() + } + } + } + binding.root.doOnAttach { Log.i("$TAG Report UI has been fully drawn (TTFD)") try { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index 16d7cc8d2..6571f09a3 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -464,7 +464,7 @@ class ConversationInfoViewModel @UiThread constructor() : AbstractConversationVi val firstParticipant = chatRoom.participants.firstOrNull() if (firstParticipant != null) { val address = firstParticipant.address - sipUri.postValue(address.asStringUriOnly()) + sipUri.postValue(LinphoneUtils.getAddressAsCleanStringUriOnly(address)) val friend = coreContext.contactsManager.findContactByAddress(address) oneToOneParticipantRefKey.postValue(friend?.refKey ?: "") } diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt index 0b1002379..b7f09f1b3 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/MainViewModel.kt @@ -84,6 +84,10 @@ class MainViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val startLoadingContactsEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + var accountsFound = -1 var mainIntentHandled = false @@ -94,6 +98,8 @@ class MainViewModel @UiThread constructor() : ViewModel() { private var alertJob: Job? = null + private var firstAccountRegistered: Boolean = false + private val coreListener = object : CoreListenerStub() { @WorkerThread override fun onLastCallEnded(core: Core) { @@ -164,10 +170,20 @@ class MainViewModel @UiThread constructor() : ViewModel() { } } RegistrationState.Ok -> { - if (account == core.defaultAccount && defaultAccountRegistrationFailed) { - Log.i("$TAG Default account is now registered") - defaultAccountRegistrationFailed = false - defaultAccountRegistrationErrorEvent.postValue(Event(false)) + if (!firstAccountRegistered) { + Log.i( + "$TAG First account registered, start loading contacts if permission has been granted" + ) + firstAccountRegistered = true + startLoadingContactsEvent.postValue(Event(true)) + } + + if (account == core.defaultAccount) { + if (defaultAccountRegistrationFailed) { + Log.i("$TAG Default account is now registered") + defaultAccountRegistrationFailed = false + defaultAccountRegistrationErrorEvent.postValue(Event(false)) + } } else { // If no call and no account is in Failed state, hide top bar val found = core.accountList.find { @@ -240,6 +256,14 @@ class MainViewModel @UiThread constructor() : ViewModel() { updateCallAlert() } atLeastOneCall.postValue(core.callsNb > 0) + + if (core.defaultAccount?.state == RegistrationState.Ok && !firstAccountRegistered) { + Log.i( + "$TAG First account registered, start loading contacts if permission has been granted" + ) + firstAccountRegistered = true + startLoadingContactsEvent.postValue(Event(true)) + } } } @@ -293,17 +317,16 @@ class MainViewModel @UiThread constructor() : ViewModel() { val currentCall = core.currentCall ?: core.calls.firstOrNull() if (currentCall != null) { - val contact = coreContext.contactsManager.findContactByAddress( - currentCall.remoteAddress - ) + val address = currentCall.remoteAddress + val contact = coreContext.contactsManager.findContactByAddress(address) val label = if (contact != null) { - contact.name ?: LinphoneUtils.getDisplayName(currentCall.remoteAddress) + contact.name ?: LinphoneUtils.getDisplayName(address) } else { val conferenceInfo = coreContext.core.findConferenceInformationFromUri( - currentCall.remoteAddress + address ) conferenceInfo?.subject ?: LinphoneUtils.getDisplayName( - currentCall.remoteAddress + address ) } addAlert(SINGLE_CALL, label) diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index f75d352c2..901daddfc 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -58,6 +58,16 @@ class LinphoneUtils { return coreContext.core.defaultAccount ?: coreContext.core.accountList.firstOrNull() } + @WorkerThread + fun getAddressAsCleanStringUriOnly(address: Address): String { + val scheme = address.scheme ?: "sip" + val username = address.username + if (username.orEmpty().isEmpty()) { + return "$scheme:${address.domain}" + } + return "$scheme:$username@${address.domain}" + } + @WorkerThread fun getDisplayName(address: Address?): String { if (address == null) return "[null]"