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

This commit is contained in:
Sylvain Berfini 2024-02-19 15:55:08 +01:00
parent aa36235ab1
commit 82d6d37fd7
6 changed files with 83 additions and 41 deletions

View file

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

View file

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

View file

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

View file

@ -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 ?: "")
}

View file

@ -84,6 +84,10 @@ class MainViewModel @UiThread constructor() : ViewModel() {
MutableLiveData<Event<Boolean>>()
}
val startLoadingContactsEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
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)

View file

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