Fixed duplicated phone numbers in loaded contacts from native addressbook + improved code by factorizing some parts of it

This commit is contained in:
Sylvain Berfini 2024-08-21 10:12:24 +02:00
parent 2821d4b72f
commit ab1ea3392d
11 changed files with 133 additions and 143 deletions

View file

@ -215,9 +215,14 @@ class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
}
if (number != null) {
val phoneNumber = Factory.instance()
.createFriendPhoneNumber(number, label)
friend.addPhoneNumberWithLabel(phoneNumber)
if (friend.phoneNumbersWithLabel.find {
PhoneNumberUtils.arePhoneNumberWeakEqual(it.phoneNumber, number)
} == null
) {
val phoneNumber = Factory.instance()
.createFriendPhoneNumber(number, label)
friend.addPhoneNumberWithLabel(phoneNumber)
}
}
}
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {

View file

@ -730,22 +730,33 @@ fun Friend.getPerson(): Person {
return personBuilder.build()
}
@WorkerThread
fun Friend.getListOfSipAddresses(): ArrayList<Address> {
val addressesList = arrayListOf<Address>()
for (address in addresses) {
if (addressesList.find { it.weakEqual(address) } == null) {
addressesList.add(address)
}
}
return addressesList
}
@WorkerThread
fun Friend.getListOfSipAddressesAndPhoneNumbers(listener: ContactNumberOrAddressClickListener): ArrayList<ContactNumberOrAddressModel> {
val addressesAndNumbers = arrayListOf<ContactNumberOrAddressModel>()
for (address in addresses) {
if (addressesAndNumbers.find { it.address?.weakEqual(address) == true } == null) {
val data = ContactNumberOrAddressModel(
this,
address,
address.asStringUriOnly(),
true, // SIP addresses are always enabled
listener,
true
)
addressesAndNumbers.add(data)
}
for (address in getListOfSipAddresses()) {
val data = ContactNumberOrAddressModel(
this,
address,
address.asStringUriOnly(),
true, // SIP addresses are always enabled
listener,
true
)
addressesAndNumbers.add(data)
}
val indexOfLastSipAddress = addressesAndNumbers.count()

View file

@ -43,7 +43,6 @@ import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel
import org.linphone.ui.main.history.viewmodel.StartCallViewModel
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
import org.linphone.ui.main.model.isEndToEndEncryptionMandatory
import org.linphone.utils.DialogUtils
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.RecyclerViewHeaderDecoration
@ -214,36 +213,19 @@ abstract class AbstractNewTransferCallFragment : GenericCallFragment() {
abstract fun action(address: Address)
private fun startCall(model: ConversationContactOrSuggestionModel) {
coreContext.postOnCoreThread { core ->
coreContext.postOnCoreThread {
val friend = model.friend
if (friend == null) {
action(model.address)
return@postOnCoreThread
}
val addressesCount = friend.addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address found for contact [${friend.name}], starting call directly"
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], starting call directly"
)
val address = friend.addresses.first()
action(address)
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
if (address != null) {
Log.i(
"$TAG Only 1 phone number found for contact [${friend.name}], starting call directly"
)
action(address)
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
action(singleAvailableAddress)
} else {
val list = friend.getListOfSipAddressesAndPhoneNumbers(listener)
Log.i(

View file

@ -34,7 +34,6 @@ import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressClickListener
import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
import org.linphone.ui.main.model.isEndToEndEncryptionMandatory
import org.linphone.ui.main.viewmodel.AddressSelectionViewModel
import org.linphone.utils.AppUtils
import org.linphone.utils.Event
@ -158,25 +157,12 @@ class ConversationForwardMessageViewModel @UiThread constructor() : AddressSelec
return@postOnCoreThread
}
val addressesCount = friend.addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
val address = friend.addresses.first()
Log.i("$TAG Only 1 SIP address found for contact [${friend.name}], using it")
onAddressSelected(address)
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
if (address != null) {
Log.i("$TAG Only 1 phone number found for contact [${friend.name}], using it")
onAddressSelected(address)
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], using it"
)
onAddressSelected(singleAvailableAddress)
} else {
val list = friend.getListOfSipAddressesAndPhoneNumbers(listener)
Log.i(

View file

@ -430,30 +430,13 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() {
@UiThread
fun startAudioCall() {
coreContext.postOnCoreThread { core ->
val addressesCount = friend.addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
coreContext.postOnCoreThread {
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address found for contact [${friend.name}], starting audio call directly"
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], starting audio call directly"
)
val address = friend.addresses.first()
coreContext.startAudioCall(address)
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
if (address != null) {
Log.i(
"$TAG Only 1 phone number found for contact [${friend.name}], starting audio call directly"
)
coreContext.startAudioCall(address)
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
coreContext.startAudioCall(singleAvailableAddress)
} else {
expectedAction = START_AUDIO_CALL
val list = sipAddressesAndPhoneNumbers.value.orEmpty()
@ -467,30 +450,13 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() {
@UiThread
fun startVideoCall() {
coreContext.postOnCoreThread { core ->
val addressesCount = friend.addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
coreContext.postOnCoreThread {
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address found for contact [${friend.name}], starting video call directly"
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], starting video call directly"
)
val address = friend.addresses.first()
coreContext.startVideoCall(address)
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
if (address != null) {
Log.i(
"$TAG Only 1 phone number found for contact [${friend.name}], starting video call directly"
)
coreContext.startVideoCall(address)
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
coreContext.startVideoCall(singleAvailableAddress)
} else {
expectedAction = START_VIDEO_CALL
val list = sipAddressesAndPhoneNumbers.value.orEmpty()
@ -504,29 +470,13 @@ class ContactViewModel @UiThread constructor() : GenericViewModel() {
@UiThread
fun goToConversation() {
coreContext.postOnCoreThread { core ->
val addressesCount = friend.addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
coreContext.postOnCoreThread {
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address found for contact [${friend.name}], sending message directly"
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], sending message directly"
)
goToConversation(friend.addresses.first())
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
if (address != null) {
Log.i(
"$TAG Only 1 phone number found for contact [${friend.name}], sending message directly"
)
goToConversation(address)
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
goToConversation(singleAvailableAddress)
} else {
expectedAction = START_CONVERSATION
val list = sipAddressesAndPhoneNumbers.value.orEmpty()

View file

@ -37,7 +37,6 @@ import org.linphone.ui.main.contacts.model.ContactNumberOrAddressModel
import org.linphone.ui.main.contacts.model.NumberOrAddressPickerDialogModel
import org.linphone.ui.main.model.ConversationContactOrSuggestionModel
import org.linphone.ui.main.model.SelectedAddressModel
import org.linphone.ui.main.model.isEndToEndEncryptionMandatory
import org.linphone.ui.main.viewmodel.AddressSelectionViewModel
import org.linphone.utils.DialogUtils
import org.linphone.utils.LinphoneUtils
@ -158,25 +157,12 @@ abstract class GenericAddressPickerFragment : GenericMainFragment() {
return@postOnCoreThread
}
val addressesCount = friend.addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
val address = friend.addresses.first()
Log.i("$TAG Only 1 SIP address found for contact [${friend.name}], using it")
onAddressSelected(address, friend)
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
if (address != null) {
Log.i("$TAG Only 1 phone number found for contact [${friend.name}], using it")
onAddressSelected(address, friend)
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
val singleAvailableAddress = LinphoneUtils.getSingleAvailableAddressForFriend(friend)
if (singleAvailableAddress != null) {
Log.i(
"$TAG Only 1 SIP address or phone number found for contact [${friend.name}], using it"
)
onAddressSelected(singleAvailableAddress, friend)
} else {
val list = friend.getListOfSipAddressesAndPhoneNumbers(listener)
Log.i(

View file

@ -28,6 +28,7 @@ import org.linphone.BuildConfig
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R
import org.linphone.contacts.ContactLoader.Companion.NATIVE_ADDRESS_BOOK_FRIEND_LIST
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.VersionUpdateCheckResult
@ -197,4 +198,20 @@ class HelpViewModel @UiThread constructor() : GenericViewModel() {
}
}
}
@UiThread
fun clearNativeFriendsDatabase() {
coreContext.postOnCoreThread { core ->
val list = core.getFriendListByName(NATIVE_ADDRESS_BOOK_FRIEND_LIST)
if (list != null) {
val friends = list.friends
Log.i("$TAG Friend list to remove found with [${friends.size}] friends")
for (friend in friends) {
list.removeFriend(friend)
}
core.removeFriendList(list)
Log.i("$TAG Friend list [$NATIVE_ADDRESS_BOOK_FRIEND_LIST] removed")
}
}
}
}

View file

@ -25,6 +25,7 @@ import androidx.annotation.IntegerRes
import androidx.annotation.WorkerThread
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R
import org.linphone.contacts.getListOfSipAddresses
import org.linphone.core.Account
import org.linphone.core.Address
import org.linphone.core.Call
@ -36,9 +37,11 @@ import org.linphone.core.ChatRoom
import org.linphone.core.ConferenceInfo
import org.linphone.core.Core
import org.linphone.core.Factory
import org.linphone.core.Friend
import org.linphone.core.Reason
import org.linphone.core.tools.Log
import org.linphone.ui.main.contacts.model.ContactAvatarModel
import org.linphone.ui.main.model.isEndToEndEncryptionMandatory
class LinphoneUtils {
companion object {
@ -95,6 +98,33 @@ class LinphoneUtils {
return address.displayName ?: address.username ?: address.asString()
}
@WorkerThread
fun getSingleAvailableAddressForFriend(friend: Friend): Address? {
val addresses = friend.getListOfSipAddresses()
val addressesCount = addresses.size
val numbersCount = friend.phoneNumbers.size
// Do not consider phone numbers if default account is in secure mode
val enablePhoneNumbers = !isEndToEndEncryptionMandatory()
if (addressesCount == 1 && (numbersCount == 0 || !enablePhoneNumbers)) {
val address = addresses.first()
Log.i("$TAG Only 1 SIP address found for contact [${friend.name}], using it")
return address
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
val number = friend.phoneNumbers.first()
val address = friend.core.interpretUrl(number, applyInternationalPrefix())
if (address != null) {
Log.i("$TAG Only 1 phone number found for contact [${friend.name}], using it")
return address
} else {
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
}
}
return null
}
@AnyThread
fun isCallIncoming(callState: Call.State): Boolean {
return when (callState) {

View file

@ -211,6 +211,27 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sdk_version_subtitle"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/settings_title_style"
android:id="@+id/change_ringtone"
android:onClick="@{() -> viewModel.clearNativeFriendsDatabase()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="@dimen/screen_bottom_margin"
android:text="@string/help_troubleshooting_clear_native_friends_in_database"
android:maxLines="1"
android:ellipsize="end"
android:drawableEnd="@drawable/trash_simple"
android:drawableTint="?attr/color_main2_600"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/show_config_file"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<include

View file

@ -169,6 +169,7 @@
<string name="help_troubleshooting_debug_logs_cleaned_toast_message">Les journaux ont été nettoyés</string>
<string name="help_troubleshooting_debug_logs_upload_error_toast_message">Echec à l\'envoi des journaux</string>
<string name="help_troubleshooting_show_config_file">Afficher la configuration</string>
<string name="help_troubleshooting_clear_native_friends_in_database">Supprimer les contacts natifs importés</string>
<!-- App & SDK settings -->
<string name="settings_title">Paramètres</string>

View file

@ -206,6 +206,7 @@
<string name="help_troubleshooting_debug_logs_cleaned_toast_message">Debug logs have been cleaned</string>
<string name="help_troubleshooting_debug_logs_upload_error_toast_message">Failed to upload debug logs</string>
<string name="help_troubleshooting_show_config_file">Show configuration</string>
<string name="help_troubleshooting_clear_native_friends_in_database">Clear imported contacts from native address book</string>
<!-- App & SDK settings -->
<string name="settings_title">Settings</string>