From ee40995f34427f2ca2f362d8cc6fa1fd11223754 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 21 Nov 2022 13:55:03 +0100 Subject: [PATCH] Fixed issue with contact editor since we switched to Friend as original value from addressbook will be lost in Friend causing edition/removal in native addressbook to silently fail --- CHANGELOG.md | 1 + .../org/linphone/activities/Navigation.kt | 16 +-- .../ContactEditorData.kt} | 110 ++++++++++++------ .../fragments/ContactEditorFragment.kt | 35 +++--- .../res/layout/contact_editor_fragment.xml | 2 +- 5 files changed, 103 insertions(+), 61 deletions(-) rename app/src/main/java/org/linphone/activities/main/contact/{viewmodels/ContactEditorViewModel.kt => data/ContactEditorData.kt} (72%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 852976508..eb6c991e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Group changes to describe their impact on the project, as follows: - Show service notification sooner to prevent crash if Core creation takes too long - Incoming call screen not being showed up to user (& screen staying off) when using app in Samsung secure folder - One to one chat room creation process waiting indefinitely if chat room already exists +- Contact edition (SIP addresses & phone numbers) not working due to original value being lost in Friend parsing - "Blinking" in some views when presence is being received - Trying to keep the preferred driver (OpenSLES / AAudio) when switching device - Issues when storing presence in native contacts + potentially duplicated SIP addresses in contact details diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index a3275454c..f6a2377b4 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -506,13 +506,15 @@ internal fun MasterContactsFragment.clearDisplayedContact() { } internal fun ContactEditorFragment.navigateToContact(id: String) { - val bundle = Bundle() - bundle.putString("id", id) - findNavController().navigate( - R.id.action_contactEditorFragment_to_detailContactFragment, - bundle, - popupTo(R.id.contactEditorFragment, true) - ) + if (findNavController().currentDestination?.id == R.id.contactEditorFragment) { + val bundle = Bundle() + bundle.putString("id", id) + findNavController().navigate( + R.id.action_contactEditorFragment_to_detailContactFragment, + bundle, + popupTo(R.id.contactEditorFragment, true) + ) + } } internal fun DetailContactFragment.navigateToChatRoom(args: Bundle?) { diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt similarity index 72% rename from app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt rename to app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt index 7518aa1bf..f64f07d26 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactEditorViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt @@ -17,43 +17,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.activities.main.contact.viewmodels +package org.linphone.activities.main.contact.data import android.graphics.Bitmap import android.graphics.BitmapFactory import android.media.ExifInterface import android.provider.ContactsContract import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope import java.io.ByteArrayOutputStream import java.io.IOException import kotlinx.coroutines.CoroutineScope import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.main.contact.data.NumberOrAddressEditorData +import org.linphone.R import org.linphone.contact.* import org.linphone.core.ChatRoomSecurityLevel import org.linphone.core.Friend import org.linphone.core.tools.Log +import org.linphone.utils.AppUtils import org.linphone.utils.ImageUtils import org.linphone.utils.PermissionHelper -class ContactEditorViewModelFactory(private val friend: Friend?) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ContactEditorViewModel(friend) as T - } -} - -class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface { +class ContactEditorData(val friend: Friend?) : ContactDataInterface { override val contact: MutableLiveData = MutableLiveData() override val displayName: MutableLiveData = MutableLiveData() override val securityLevel: MutableLiveData = MutableLiveData() - override val coroutineScope: CoroutineScope = viewModelScope + override val coroutineScope: CoroutineScope = coreContext.coroutineScope val firstName = MutableLiveData() @@ -74,20 +63,20 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface var syncAccountType: String? = null init { - if (c != null) { - contact.value = c!! - displayName.value = c.name ?: "" + if (friend != null) { + contact.value = friend!! + displayName.value = friend.name ?: "" } else { displayName.value = "" } - organization.value = c?.organization ?: "" + organization.value = friend?.organization ?: "" firstName.value = "" lastName.value = "" - val vCard = c?.vcard + val refKey = friend?.refKey + val vCard = friend?.vcard if (vCard?.familyName.isNullOrEmpty() && vCard?.givenName.isNullOrEmpty()) { - val refKey = c?.refKey if (refKey != null) { Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now") fetchFirstAndLastNames(refKey) @@ -99,11 +88,11 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface lastName.value = vCard?.familyName } - updateNumbersAndAddresses() + updateNumbersAndAddresses(refKey) } fun save(): Friend { - var contact = c + var contact = friend var created = false if (contact == null) { @@ -144,7 +133,7 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface } for (address in addresses.value.orEmpty()) { val sipAddress = address.newValue.value.orEmpty() - if (sipAddress.isEmpty()) continue + if (sipAddress.isEmpty() || address.toRemove.value == true) continue val parsed = coreContext.core.interpretUrl(sipAddress, false) if (parsed != null) contact.addAddress(parsed) @@ -155,7 +144,7 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface } for (phone in numbers.value.orEmpty()) { val phoneNumber = phone.newValue.value.orEmpty() - if (phoneNumber.isEmpty()) continue + if (phoneNumber.isEmpty() || phone.toRemove.value == true) continue contact.addPhoneNumber(phoneNumber) } @@ -228,20 +217,71 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface numbers.value = list } - private fun updateNumbersAndAddresses() { + private fun updateNumbersAndAddresses(contactId: String?) { val phoneNumbers = arrayListOf() - for (number in c?.phoneNumbers.orEmpty()) { - phoneNumbers.add(NumberOrAddressEditorData(number, false)) + val sipAddresses = arrayListOf() + var fetched = false + + if (contactId != null) { + try { + // Try to get real values from contact to ensure edition/removal in native address book will go well + val cursor = coreContext.context.contentResolver.query( + ContactsContract.Data.CONTENT_URI, + arrayOf( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.NUMBER + ), + ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?", + arrayOf(contactId), + null + ) + + while (cursor != null && cursor.moveToNext()) { + val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type) + val mime: String? = + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE)) + if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) { + val data1: String? = + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)) + if (data1 != null) { + phoneNumbers.add(NumberOrAddressEditorData(data1, false)) + } + } else if ( + mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE || + mime == linphoneMime + ) { + val data1: String? = + cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS)) + if (data1 != null) { + sipAddresses.add(NumberOrAddressEditorData(data1, true)) + } + } + } + + cursor?.close() + fetched = true + } catch (e: Exception) { + Log.e("[Contact Editor] Failed to sip addresses & phone number: $e") + fetched = false + } } + + if (!fetched) { + Log.w("[Contact Editor] Fall-backing to friend info (might be inaccurate and thus edition/removal might fail)") + for (number in friend?.phoneNumbers.orEmpty()) { + phoneNumbers.add(NumberOrAddressEditorData(number, false)) + } + + for (address in friend?.addresses.orEmpty()) { + sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true)) + } + } + if (phoneNumbers.isEmpty()) { phoneNumbers.add(NumberOrAddressEditorData("", false)) } numbers.value = phoneNumbers - val sipAddresses = arrayListOf() - for (address in c?.addresses.orEmpty()) { - sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true)) - } if (sipAddresses.isEmpty()) { sipAddresses.add(NumberOrAddressEditorData("", true)) } @@ -268,14 +308,14 @@ class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface val givenName: String? = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)) if (!givenName.isNullOrEmpty()) { - c?.vcard?.givenName = givenName + friend?.vcard?.givenName = givenName firstName.value = givenName!! } val familyName: String? = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)) if (!familyName.isNullOrEmpty()) { - c?.vcard?.familyName = familyName + friend?.vcard?.familyName = familyName lastName.value = familyName!! } } diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt index 1f2efd3f8..cdce1f3ff 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt @@ -27,7 +27,6 @@ import android.os.Parcelable import android.provider.MediaStore import android.view.View import androidx.core.content.FileProvider -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import java.io.File import kotlinx.coroutines.launch @@ -35,6 +34,7 @@ import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R import org.linphone.activities.GenericFragment import org.linphone.activities.main.MainActivity +import org.linphone.activities.main.contact.data.ContactEditorData import org.linphone.activities.main.contact.data.NumberOrAddressEditorData import org.linphone.activities.main.contact.viewmodels.* import org.linphone.activities.navigateToContact @@ -44,7 +44,7 @@ import org.linphone.utils.FileUtils import org.linphone.utils.PermissionHelper class ContactEditorFragment : GenericFragment(), SyncAccountPickerFragment.SyncAccountPickedListener { - private lateinit var viewModel: ContactEditorViewModel + private lateinit var data: ContactEditorData private var temporaryPicturePath: File? = null override fun getLayoutId(): Int = R.layout.contact_editor_fragment @@ -54,11 +54,10 @@ class ContactEditorFragment : GenericFragment(), S binding.lifecycleOwner = viewLifecycleOwner - viewModel = ViewModelProvider( - this, - ContactEditorViewModelFactory(sharedViewModel.selectedContact.value) - )[ContactEditorViewModel::class.java] - binding.viewModel = viewModel + val contact = sharedViewModel.selectedContact.value + // TODO: FIXME: contact can be const! Find a way to get it not-const! + data = ContactEditorData(contact) + binding.viewModel = data useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false @@ -67,10 +66,10 @@ class ContactEditorFragment : GenericFragment(), S } binding.setSaveChangesClickListener { - viewModel.syncAccountName = null - viewModel.syncAccountType = null + data.syncAccountName = null + data.syncAccountType = null - if (viewModel.c == null && corePreferences.showNewContactAccountDialog) { + if (data.friend == null && corePreferences.showNewContactAccountDialog) { Log.i("[Contact Editor] New contact, ask user where to store it") SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker") } else { @@ -85,9 +84,9 @@ class ContactEditorFragment : GenericFragment(), S newSipUri.newValue.value = sipUri val list = arrayListOf() - list.addAll(viewModel.addresses.value.orEmpty()) + list.addAll(data.addresses.value.orEmpty()) list.add(newSipUri) - viewModel.addresses.value = list + data.addresses.value = list } if (!PermissionHelper.required(requireContext()).hasWriteContactsPermission()) { @@ -98,8 +97,8 @@ class ContactEditorFragment : GenericFragment(), S override fun onSyncAccountClicked(name: String?, type: String?) { Log.i("[Contact Editor] Using account $name / $type") - viewModel.syncAccountName = name - viewModel.syncAccountType = type + data.syncAccountName = name + data.syncAccountType = type saveContact() } @@ -122,19 +121,19 @@ class ContactEditorFragment : GenericFragment(), S } @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { if (resultCode == Activity.RESULT_OK) { lifecycleScope.launch { - val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) + val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(intent, temporaryPicturePath) if (contactImageFilePath != null) { - viewModel.setPictureFromPath(contactImageFilePath) + data.setPictureFromPath(contactImageFilePath) } } } } private fun saveContact() { - val savedContact = viewModel.save() + val savedContact = data.save() val id = savedContact.refKey if (id != null) { Log.i("[Contact Editor] Displaying contact $savedContact") diff --git a/app/src/main/res/layout/contact_editor_fragment.xml b/app/src/main/res/layout/contact_editor_fragment.xml index 76c888b37..235d4c0fc 100644 --- a/app/src/main/res/layout/contact_editor_fragment.xml +++ b/app/src/main/res/layout/contact_editor_fragment.xml @@ -12,7 +12,7 @@ type="android.view.View.OnClickListener"/> + type="org.linphone.activities.main.contact.data.ContactEditorData" />