Reworked native address book integration, removed Contact & NativeContact objects to directly rely on Friend
This commit is contained in:
parent
f83eb5e6b1
commit
341c112d54
67 changed files with 838 additions and 1346 deletions
|
@ -49,7 +49,6 @@ import org.linphone.activities.main.settings.fragments.*
|
|||
import org.linphone.activities.main.sidemenu.fragments.SideMenuFragment
|
||||
import org.linphone.activities.voip.CallActivity
|
||||
import org.linphone.activities.voip.fragments.*
|
||||
import org.linphone.contact.NativeContact
|
||||
import org.linphone.core.Address
|
||||
|
||||
internal fun Fragment.findMasterNavController(): NavController {
|
||||
|
@ -534,9 +533,9 @@ internal fun MasterContactsFragment.clearDisplayedContact() {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun ContactEditorFragment.navigateToContact(contact: NativeContact) {
|
||||
internal fun ContactEditorFragment.navigateToContact(id: String) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString("id", contact.nativeId)
|
||||
bundle.putString("id", id)
|
||||
findNavController().navigate(
|
||||
R.id.action_contactEditorFragment_to_detailContactFragment,
|
||||
bundle,
|
||||
|
@ -653,8 +652,8 @@ internal fun DetailCallLogFragment.navigateToContacts(sipUriToAdd: String) {
|
|||
findMasterNavController().navigate(Uri.parse(deepLink))
|
||||
}
|
||||
|
||||
internal fun DetailCallLogFragment.navigateToContact(contact: NativeContact) {
|
||||
val deepLink = "linphone-android://contact/view/${contact.nativeId}"
|
||||
internal fun DetailCallLogFragment.navigateToContact(id: String) {
|
||||
val deepLink = "linphone-android://contact/view/$id"
|
||||
findMasterNavController().navigate(Uri.parse(deepLink))
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ class EventData(private val eventLog: EventLog) : GenericContactData(
|
|||
}
|
||||
|
||||
private fun getName(): String {
|
||||
return contact.value?.fullName ?: displayName.value ?: ""
|
||||
return contact.value?.name ?: displayName.value ?: ""
|
||||
}
|
||||
|
||||
private fun updateEventText() {
|
||||
|
|
|
@ -166,16 +166,6 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
|
|||
}
|
||||
}
|
||||
|
||||
override fun goBack() {
|
||||
if (!findNavController().popBackStack()) {
|
||||
if (sharedViewModel.isSlidingPaneSlideable.value == true) {
|
||||
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
|
||||
} else {
|
||||
navigateToEmptyChatRoom()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
|
@ -185,14 +175,23 @@ class ChatRoomCreationFragment : SecureFragment<ChatRoomCreationFragmentBinding>
|
|||
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
if (granted) {
|
||||
Log.i("[Chat Room Creation] READ_CONTACTS permission granted")
|
||||
coreContext.contactsManager.onReadContactsPermissionGranted()
|
||||
coreContext.contactsManager.fetchContactsAsync()
|
||||
coreContext.fetchContacts()
|
||||
} else {
|
||||
Log.w("[Chat Room Creation] READ_CONTACTS permission denied")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun goBack() {
|
||||
if (!findNavController().popBackStack()) {
|
||||
if (sharedViewModel.isSlidingPaneSlideable.value == true) {
|
||||
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
|
||||
} else {
|
||||
navigateToEmptyChatRoom()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addParticipantsFromSharedViewModel() {
|
||||
val participants = sharedViewModel.chatRoomParticipants.value
|
||||
if (participants != null && participants.size > 0) {
|
||||
|
|
|
@ -30,7 +30,6 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.ContactDataInterface
|
||||
import org.linphone.contact.ContactsUpdatedListenerStub
|
||||
import org.linphone.core.*
|
||||
|
@ -49,7 +48,7 @@ class ChatRoomViewModelFactory(private val chatRoom: ChatRoom) :
|
|||
}
|
||||
|
||||
class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterface {
|
||||
override val contact: MutableLiveData<Contact> = MutableLiveData<Contact>()
|
||||
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||
override val showGroupChatAvatar: Boolean = chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) &&
|
||||
|
@ -290,7 +289,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
}
|
||||
|
||||
val sender: String =
|
||||
coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.fullName
|
||||
coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.name
|
||||
?: LinphoneUtils.getDisplayName(msg.fromAddress)
|
||||
builder.append(sender)
|
||||
builder.append(": ")
|
||||
|
@ -332,9 +331,8 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
var participantsList = ""
|
||||
var index = 0
|
||||
for (participant in chatRoom.participants) {
|
||||
val contact: Contact? =
|
||||
coreContext.contactsManager.findContactByAddress(participant.address)
|
||||
participantsList += contact?.fullName ?: LinphoneUtils.getDisplayName(participant.address)
|
||||
val contact = coreContext.contactsManager.findContactByAddress(participant.address)
|
||||
participantsList += contact?.name ?: LinphoneUtils.getDisplayName(participant.address)
|
||||
index++
|
||||
if (index != chatRoom.nbParticipants) participantsList += ", "
|
||||
}
|
||||
|
@ -361,9 +359,9 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
|||
|
||||
var composing = ""
|
||||
for (address in chatRoom.composingAddresses) {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(address)
|
||||
val contact = coreContext.contactsManager.findContactByAddress(address)
|
||||
composing += if (composing.isNotEmpty()) ", " else ""
|
||||
composing += contact?.fullName ?: LinphoneUtils.getDisplayName(address)
|
||||
composing += contact?.name ?: LinphoneUtils.getDisplayName(address)
|
||||
}
|
||||
composingList.value = AppUtils.getStringWithPlural(R.plurals.chat_room_remote_composing, chatRoom.composingAddresses.size, composing)
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo) {
|
|||
|
||||
val contact = coreContext.contactsManager.findContactByAddress(organizerAddress)
|
||||
organizer.value = if (contact != null)
|
||||
contact.fullName
|
||||
contact.name
|
||||
else
|
||||
LinphoneUtils.getDisplayName(conferenceInfo.organizer)
|
||||
} else {
|
||||
|
@ -96,7 +96,7 @@ class ScheduledConferenceData(val conferenceInfo: ConferenceInfo) {
|
|||
|
||||
for (participant in conferenceInfo.participants) {
|
||||
val contact = coreContext.contactsManager.findContactByAddress(participant)
|
||||
val name = if (contact != null) contact.fullName else LinphoneUtils.getDisplayName(participant)
|
||||
val name = if (contact != null) contact.name else LinphoneUtils.getDisplayName(participant)
|
||||
val address = participant.asStringUriOnly()
|
||||
participantsListShort += "$name, "
|
||||
participantsListExpanded += "$name ($address)\n"
|
||||
|
|
|
@ -111,8 +111,7 @@ class ConferenceSchedulingParticipantsListFragment : GenericFragment<ConferenceS
|
|||
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
if (granted) {
|
||||
Log.i("[Conference Creation] READ_CONTACTS permission granted")
|
||||
coreContext.contactsManager.onReadContactsPermissionGranted()
|
||||
coreContext.contactsManager.fetchContactsAsync()
|
||||
coreContext.fetchContacts()
|
||||
} else {
|
||||
Log.w("[Conference Creation] READ_CONTACTS permission denied")
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.linphone.R
|
|||
import org.linphone.activities.main.adapters.SelectionListAdapter
|
||||
import org.linphone.activities.main.contact.viewmodels.ContactViewModel
|
||||
import org.linphone.activities.main.viewmodels.ListTopBarViewModel
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.databinding.ContactListCellBinding
|
||||
import org.linphone.databinding.GenericListHeaderBinding
|
||||
import org.linphone.utils.AppUtils
|
||||
|
@ -43,8 +43,8 @@ class ContactsListAdapter(
|
|||
selectionVM: ListTopBarViewModel,
|
||||
private val viewLifecycleOwner: LifecycleOwner
|
||||
) : SelectionListAdapter<ContactViewModel, RecyclerView.ViewHolder>(selectionVM, ContactDiffCallback()), HeaderAdapter {
|
||||
val selectedContactEvent: MutableLiveData<Event<Contact>> by lazy {
|
||||
MutableLiveData<Event<Contact>>()
|
||||
val selectedContactEvent: MutableLiveData<Event<Friend>> by lazy {
|
||||
MutableLiveData<Event<Friend>>()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
|
@ -80,7 +80,9 @@ class ContactsListAdapter(
|
|||
if (selectionViewModel.isEditionEnabled.value == true) {
|
||||
selectionViewModel.onToggleSelect(bindingAdapterPosition)
|
||||
} else {
|
||||
selectedContactEvent.value = Event(contactViewModel.contactInternal)
|
||||
val friend = contactViewModel.contact.value
|
||||
// TODO FIXME !!!
|
||||
if (friend != null) selectedContactEvent.value = Event(friend)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,17 +103,17 @@ class ContactsListAdapter(
|
|||
override fun displayHeaderForPosition(position: Int): Boolean {
|
||||
if (position >= itemCount) return false
|
||||
val contact = getItem(position)
|
||||
val firstLetter = contact.name.first().toString()
|
||||
val firstLetter = contact.fullName.firstOrNull().toString()
|
||||
val previousPosition = position - 1
|
||||
return if (previousPosition >= 0) {
|
||||
val previousItemFirstLetter = getItem(previousPosition).name.first().toString()
|
||||
val previousItemFirstLetter = getItem(previousPosition).fullName.firstOrNull().toString()
|
||||
!firstLetter.equals(previousItemFirstLetter, ignoreCase = true)
|
||||
} else true
|
||||
}
|
||||
|
||||
override fun getHeaderViewForPosition(context: Context, position: Int): View {
|
||||
val contact = getItem(position)
|
||||
val firstLetter = AppUtils.getInitials(contact.name, 1)
|
||||
val firstLetter = AppUtils.getInitials(contact.fullName, 1)
|
||||
val binding: GenericListHeaderBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.generic_list_header, null, false
|
||||
|
@ -127,7 +129,7 @@ private class ContactDiffCallback : DiffUtil.ItemCallback<ContactViewModel>() {
|
|||
oldItem: ContactViewModel,
|
||||
newItem: ContactViewModel
|
||||
): Boolean {
|
||||
return oldItem.contactInternal.compareTo(newItem.contactInternal) == 0
|
||||
return oldItem.fullName.compareTo(newItem.fullName) == 0
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
|
|
|
@ -41,7 +41,6 @@ import org.linphone.activities.main.contact.viewmodels.*
|
|||
import org.linphone.activities.main.viewmodels.SharedMainViewModel
|
||||
import org.linphone.activities.navigateToContact
|
||||
import org.linphone.activities.navigateToEmptyContact
|
||||
import org.linphone.contact.NativeContact
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.ContactEditorFragmentBinding
|
||||
import org.linphone.utils.Event
|
||||
|
@ -157,10 +156,10 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
|
|||
|
||||
private fun saveContact() {
|
||||
val savedContact = viewModel.save()
|
||||
if (savedContact is NativeContact) {
|
||||
savedContact.syncValuesFromAndroidContact(requireContext())
|
||||
val id = savedContact.refKey
|
||||
if (id != null) {
|
||||
Log.i("[Contact Editor] Displaying contact $savedContact")
|
||||
navigateToContact(savedContact)
|
||||
navigateToContact(id)
|
||||
} else {
|
||||
goBack()
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
|
|||
|
||||
val contact = sharedViewModel.selectedContact.value
|
||||
if (contact == null) {
|
||||
Log.e("[Contact] Contact is null, aborting!")
|
||||
Log.e("[Contact] Friend is null, aborting!")
|
||||
goBack()
|
||||
return
|
||||
}
|
||||
|
@ -146,6 +146,7 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
|
|||
(activity as MainActivity).showSnackBar(messageResourceId)
|
||||
}
|
||||
}
|
||||
viewModel.updateNumbersAndAddresses()
|
||||
|
||||
view.doOnPreDraw {
|
||||
// Notifies fragment is ready to be drawn
|
||||
|
@ -153,6 +154,22 @@ class DetailContactFragment : GenericFragment<ContactDetailFragmentBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (this::viewModel.isInitialized) {
|
||||
viewModel.registerContactListener()
|
||||
coreContext.contactsManager.contactIdToWatchFor = viewModel.contact.value?.refKey ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
coreContext.contactsManager.contactIdToWatchFor = ""
|
||||
if (this::viewModel.isInitialized) {
|
||||
viewModel.unregisterContactListener()
|
||||
}
|
||||
}
|
||||
|
||||
override fun goBack() {
|
||||
if (!findNavController().popBackStack()) {
|
||||
if (sharedViewModel.isSlidingPaneSlideable.value == true) {
|
||||
|
|
|
@ -46,8 +46,8 @@ import org.linphone.activities.main.viewmodels.DialogViewModel
|
|||
import org.linphone.activities.main.viewmodels.SharedMainViewModel
|
||||
import org.linphone.activities.navigateToContact
|
||||
import org.linphone.activities.navigateToContactEditor
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.core.Factory
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.ContactMasterFragmentBinding
|
||||
import org.linphone.utils.*
|
||||
|
@ -188,13 +188,15 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
|
||||
viewModel.showDeleteButton(
|
||||
{
|
||||
val deletedContact = adapter.currentList[viewHolder.bindingAdapterPosition].contactInternal
|
||||
listViewModel.deleteContact(deletedContact)
|
||||
if (!binding.slidingPane.isSlideable &&
|
||||
deletedContact == sharedViewModel.selectedContact.value
|
||||
) {
|
||||
Log.i("[Contacts] Currently displayed contact has been deleted, removing detail fragment")
|
||||
clearDisplayedContact()
|
||||
val deletedContact = adapter.currentList[viewHolder.bindingAdapterPosition].contact.value
|
||||
if (deletedContact != null) {
|
||||
listViewModel.deleteContact(deletedContact)
|
||||
if (!binding.slidingPane.isSlideable &&
|
||||
deletedContact == sharedViewModel.selectedContact.value
|
||||
) {
|
||||
Log.i("[Contacts] Currently displayed contact has been deleted, removing detail fragment")
|
||||
clearDisplayedContact()
|
||||
}
|
||||
}
|
||||
dialog.dismiss()
|
||||
},
|
||||
|
@ -218,7 +220,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
viewLifecycleOwner
|
||||
) {
|
||||
it.consume { contact ->
|
||||
Log.i("[Contacts] Selected item in list changed: $contact")
|
||||
Log.d("[Contacts] Selected item in list changed: $contact")
|
||||
sharedViewModel.selectedContact.value = contact
|
||||
(requireActivity() as MainActivity).hideKeyboard()
|
||||
|
||||
|
@ -232,6 +234,12 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
}
|
||||
}
|
||||
|
||||
coreContext.contactsManager.fetchInProgress.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
listViewModel.fetchInProgress.value = it
|
||||
}
|
||||
|
||||
listViewModel.contactsList.observe(
|
||||
viewLifecycleOwner
|
||||
) {
|
||||
|
@ -320,11 +328,13 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
}
|
||||
|
||||
override fun deleteItems(indexesOfItemToDelete: ArrayList<Int>) {
|
||||
val list = ArrayList<Contact>()
|
||||
val list = ArrayList<Friend>()
|
||||
var closeSlidingPane = false
|
||||
for (index in indexesOfItemToDelete) {
|
||||
val contact = adapter.currentList[index].contactInternal
|
||||
list.add(contact)
|
||||
val contact = adapter.currentList[index].contact.value
|
||||
if (contact != null) {
|
||||
list.add(contact)
|
||||
}
|
||||
|
||||
if (contact == sharedViewModel.selectedContact.value) {
|
||||
closeSlidingPane = true
|
||||
|
@ -347,8 +357,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
|||
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
if (granted) {
|
||||
Log.i("[Contacts] READ_CONTACTS permission granted")
|
||||
coreContext.contactsManager.onReadContactsPermissionGranted()
|
||||
coreContext.contactsManager.fetchContactsAsync()
|
||||
coreContext.fetchContacts()
|
||||
} else {
|
||||
Log.w("[Contacts] READ_CONTACTS permission denied")
|
||||
}
|
||||
|
|
|
@ -32,21 +32,22 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
|
|||
import org.linphone.activities.main.contact.data.NumberOrAddressEditorData
|
||||
import org.linphone.contact.*
|
||||
import org.linphone.core.ChatRoomSecurityLevel
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.ImageUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
class ContactEditorViewModelFactory(private val contact: Contact?) :
|
||||
class ContactEditorViewModelFactory(private val friend: Friend?) :
|
||||
ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ContactEditorViewModel(contact) as T
|
||||
return ContactEditorViewModel(friend) as T
|
||||
}
|
||||
}
|
||||
|
||||
class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactDataInterface {
|
||||
override val contact: MutableLiveData<Contact> = MutableLiveData<Contact>()
|
||||
class ContactEditorViewModel(val c: Friend?) : ViewModel(), ContactDataInterface {
|
||||
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||
|
||||
|
@ -71,30 +72,37 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactDataInterfac
|
|||
init {
|
||||
if (c != null) {
|
||||
contact.value = c!!
|
||||
displayName.value = c.fullName ?: c.firstName + " " + c.lastName
|
||||
displayName.value = c.name ?: ""
|
||||
} else {
|
||||
displayName.value = ""
|
||||
}
|
||||
firstName.value = c?.firstName ?: ""
|
||||
lastName.value = c?.lastName ?: ""
|
||||
organization.value = c?.organization ?: ""
|
||||
firstName.value = c?.vcard?.givenName ?: ""
|
||||
lastName.value = c?.vcard?.familyName ?: ""
|
||||
organization.value = c?.vcard?.organization ?: ""
|
||||
|
||||
updateNumbersAndAddresses()
|
||||
}
|
||||
|
||||
fun save(): Contact {
|
||||
fun save(): Friend {
|
||||
var contact = c
|
||||
var created = false
|
||||
|
||||
if (contact == null) {
|
||||
created = true
|
||||
contact = if (PermissionHelper.get().hasWriteContactsPermission()) {
|
||||
NativeContact(NativeContactEditor.createAndroidContact(syncAccountName, syncAccountType).toString())
|
||||
val nativeId = if (PermissionHelper.get().hasWriteContactsPermission()) {
|
||||
Log.i("[Contact Editor] Creating native contact")
|
||||
NativeContactEditor.createAndroidContact(syncAccountName, syncAccountType)
|
||||
.toString()
|
||||
} else {
|
||||
Contact()
|
||||
Log.e("[Contact Editor] Can't native contact, permission denied")
|
||||
null
|
||||
}
|
||||
contact = coreContext.core.createFriend()
|
||||
contact.refKey = nativeId
|
||||
}
|
||||
|
||||
if (contact is NativeContact) {
|
||||
if (contact.refKey != null) {
|
||||
Log.i("[Contact Editor] Committing changes in native contact id ${contact.refKey}")
|
||||
NativeContactEditor(contact)
|
||||
.setFirstAndLastNames(firstName.value.orEmpty(), lastName.value.orEmpty())
|
||||
.setOrganization(organization.value.orEmpty())
|
||||
|
@ -102,45 +110,44 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactDataInterfac
|
|||
.setSipAddresses(addresses.value.orEmpty())
|
||||
.setPicture(picture)
|
||||
.commit()
|
||||
} else {
|
||||
val friend = contact.friend ?: coreContext.core.createFriend()
|
||||
friend.edit()
|
||||
friend.name = "${firstName.value.orEmpty()} ${lastName.value.orEmpty()}"
|
||||
}
|
||||
|
||||
for (address in friend.addresses) {
|
||||
friend.removeAddress(address)
|
||||
}
|
||||
for (address in addresses.value.orEmpty()) {
|
||||
val parsed = coreContext.core.interpretUrl(address.newValue.value.orEmpty())
|
||||
if (parsed != null) friend.addAddress(parsed)
|
||||
}
|
||||
if (!created) contact.edit()
|
||||
|
||||
for (phone in friend.phoneNumbers) {
|
||||
friend.removePhoneNumber(phone)
|
||||
}
|
||||
for (phone in numbers.value.orEmpty()) {
|
||||
val phoneNumber = phone.newValue.value
|
||||
if (phoneNumber?.isNotEmpty() == true) {
|
||||
friend.addPhoneNumber(phoneNumber)
|
||||
}
|
||||
}
|
||||
contact.name = "${firstName.value.orEmpty()} ${lastName.value.orEmpty()}"
|
||||
contact.organization = organization.value
|
||||
|
||||
val vCard = friend.vcard
|
||||
if (vCard != null) {
|
||||
vCard.organization = organization.value
|
||||
vCard.familyName = lastName.value
|
||||
vCard.givenName = firstName.value
|
||||
}
|
||||
friend.done()
|
||||
for (address in contact.addresses) {
|
||||
contact.removeAddress(address)
|
||||
}
|
||||
for (address in addresses.value.orEmpty()) {
|
||||
val sipAddress = address.newValue.value.orEmpty()
|
||||
if (sipAddress.isEmpty()) continue
|
||||
|
||||
if (contact.friend == null) {
|
||||
contact.friend = friend
|
||||
coreContext.core.defaultFriendList?.addLocalFriend(friend)
|
||||
}
|
||||
val parsed = coreContext.core.interpretUrl(sipAddress)
|
||||
if (parsed != null) contact.addAddress(parsed)
|
||||
}
|
||||
|
||||
for (phone in contact.phoneNumbers) {
|
||||
contact.removePhoneNumber(phone)
|
||||
}
|
||||
for (phone in numbers.value.orEmpty()) {
|
||||
val phoneNumber = phone.newValue.value.orEmpty()
|
||||
if (phoneNumber.isEmpty()) continue
|
||||
|
||||
contact.addPhoneNumber(phoneNumber)
|
||||
}
|
||||
|
||||
val vCard = contact.vcard
|
||||
if (vCard != null) {
|
||||
vCard.familyName = lastName.value
|
||||
vCard.givenName = firstName.value
|
||||
}
|
||||
|
||||
if (created) {
|
||||
coreContext.contactsManager.addContact(contact)
|
||||
coreContext.core.defaultFriendList?.addLocalFriend(contact)
|
||||
} else {
|
||||
contact.done()
|
||||
}
|
||||
return contact
|
||||
}
|
||||
|
@ -201,8 +208,8 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactDataInterfac
|
|||
|
||||
private fun updateNumbersAndAddresses() {
|
||||
val phoneNumbers = arrayListOf<NumberOrAddressEditorData>()
|
||||
for (number in c?.rawPhoneNumbers.orEmpty()) {
|
||||
phoneNumbers.add(NumberOrAddressEditorData(number, false))
|
||||
for (number in c?.phoneNumbersWithLabel.orEmpty()) {
|
||||
phoneNumbers.add(NumberOrAddressEditorData(number.phoneNumber, false))
|
||||
}
|
||||
if (phoneNumbers.isEmpty()) {
|
||||
phoneNumbers.add(NumberOrAddressEditorData("", false))
|
||||
|
@ -210,8 +217,8 @@ class ContactEditorViewModel(val c: Contact?) : ViewModel(), ContactDataInterfac
|
|||
numbers.value = phoneNumbers
|
||||
|
||||
val sipAddresses = arrayListOf<NumberOrAddressEditorData>()
|
||||
for (address in c?.rawSipAddresses.orEmpty()) {
|
||||
sipAddresses.add(NumberOrAddressEditorData(address, true))
|
||||
for (address in c?.addresses.orEmpty()) {
|
||||
sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true))
|
||||
}
|
||||
if (sipAddresses.isEmpty()) {
|
||||
sipAddresses.add(NumberOrAddressEditorData("", true))
|
||||
|
|
|
@ -30,31 +30,29 @@ import org.linphone.R
|
|||
import org.linphone.activities.main.contact.data.ContactNumberOrAddressClickListener
|
||||
import org.linphone.activities.main.contact.data.ContactNumberOrAddressData
|
||||
import org.linphone.activities.main.viewmodels.MessageNotifierViewModel
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.ContactDataInterface
|
||||
import org.linphone.contact.ContactsUpdatedListenerStub
|
||||
import org.linphone.contact.NativeContact
|
||||
import org.linphone.contact.hasPresence
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ContactViewModelFactory(private val contact: Contact) :
|
||||
class ContactViewModelFactory(private val friend: Friend) :
|
||||
ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ContactViewModel(contact) as T
|
||||
return ContactViewModel(friend) as T
|
||||
}
|
||||
}
|
||||
|
||||
class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel(), ContactDataInterface {
|
||||
override val contact: MutableLiveData<Contact> = MutableLiveData<Contact>()
|
||||
class ContactViewModel(friend: Friend, async: Boolean = false) : MessageNotifierViewModel(), ContactDataInterface {
|
||||
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||
|
||||
val name: String
|
||||
get() = displayName.value ?: ""
|
||||
var fullName = ""
|
||||
|
||||
val displayOrganization = corePreferences.displayOrganization
|
||||
|
||||
|
@ -76,28 +74,33 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel(
|
|||
|
||||
val isNativeContact = MutableLiveData<Boolean>()
|
||||
|
||||
private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() {
|
||||
override fun onContactUpdated(contact: Contact) {
|
||||
if (contact is NativeContact && contactInternal is NativeContact && contact.nativeId == contactInternal.nativeId) {
|
||||
Log.d("[Contact] $contact has changed")
|
||||
updateNumbersAndAddresses(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val chatRoomListener = object : ChatRoomListenerStub() {
|
||||
override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) {
|
||||
if (state == ChatRoom.State.Created) {
|
||||
chatRoom.removeListener(this)
|
||||
waitForChatRoomCreation.value = false
|
||||
chatRoomCreatedEvent.value = Event(chatRoom)
|
||||
} else if (state == ChatRoom.State.CreationFailed) {
|
||||
Log.e("[Contact Detail] Group chat room creation has failed !")
|
||||
chatRoom.removeListener(this)
|
||||
waitForChatRoomCreation.value = false
|
||||
onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val contactsListener = object : ContactsUpdatedListenerStub() {
|
||||
override fun onContactUpdated(friend: Friend) {
|
||||
if (friend.refKey == contact.value?.refKey) {
|
||||
Log.i("[Contact Detail] Friend has been updated!")
|
||||
contact.value = friend
|
||||
displayName.value = friend.name
|
||||
isNativeContact.value = friend.refKey != null
|
||||
updateNumbersAndAddresses()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val listener = object : ContactNumberOrAddressClickListener {
|
||||
override fun onCall(address: Address) {
|
||||
startCallToEvent.value = Event(address)
|
||||
|
@ -127,13 +130,17 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel(
|
|||
}
|
||||
|
||||
init {
|
||||
contact.value = contactInternal
|
||||
displayName.value = contactInternal.fullName ?: contactInternal.firstName + " " + contactInternal.lastName
|
||||
isNativeContact.value = contactInternal is NativeContact
|
||||
fullName = friend.name ?: ""
|
||||
|
||||
updateNumbersAndAddresses(contactInternal)
|
||||
coreContext.contactsManager.addListener(contactsUpdatedListener)
|
||||
waitForChatRoomCreation.value = false
|
||||
if (async) {
|
||||
contact.postValue(friend)
|
||||
displayName.postValue(friend.name)
|
||||
isNativeContact.postValue(friend.refKey != null)
|
||||
} else {
|
||||
contact.value = friend
|
||||
displayName.value = friend.name
|
||||
isNativeContact.value = friend.refKey != null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
@ -142,17 +149,24 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel(
|
|||
}
|
||||
|
||||
fun destroy() {
|
||||
coreContext.contactsManager.removeListener(contactsUpdatedListener)
|
||||
}
|
||||
|
||||
fun registerContactListener() {
|
||||
coreContext.contactsManager.addListener(contactsListener)
|
||||
}
|
||||
|
||||
fun unregisterContactListener() {
|
||||
coreContext.contactsManager.removeListener(contactsListener)
|
||||
}
|
||||
|
||||
fun deleteContact() {
|
||||
val select = ContactsContract.Data.CONTACT_ID + " = ?"
|
||||
val ops = java.util.ArrayList<ContentProviderOperation>()
|
||||
|
||||
if (contactInternal is NativeContact) {
|
||||
val nativeContact: NativeContact = contactInternal
|
||||
Log.i("[Contact] Setting Android contact id ${nativeContact.nativeId} to batch removal")
|
||||
val args = arrayOf(nativeContact.nativeId)
|
||||
val id = contact.value?.refKey
|
||||
if (id != null) {
|
||||
Log.i("[Contact] Setting Android contact id $id to batch removal")
|
||||
val args = arrayOf(id)
|
||||
ops.add(
|
||||
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withSelection(select, args)
|
||||
|
@ -160,10 +174,7 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
if (contactInternal.friend != null) {
|
||||
Log.i("[Contact] Removing friend")
|
||||
contactInternal.friend?.remove()
|
||||
}
|
||||
contact.value?.remove()
|
||||
|
||||
if (ops.isNotEmpty()) {
|
||||
try {
|
||||
|
@ -175,30 +186,37 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun updateNumbersAndAddresses(contact: Contact) {
|
||||
fun updateNumbersAndAddresses() {
|
||||
val list = arrayListOf<ContactNumberOrAddressData>()
|
||||
for (address in contact.sipAddresses) {
|
||||
val friend = contact.value ?: return
|
||||
|
||||
for (address in friend.addresses) {
|
||||
val value = address.asStringUriOnly()
|
||||
val presenceModel = contact.friend?.getPresenceModelForUriOrTel(value)
|
||||
val presenceModel = friend.getPresenceModelForUriOrTel(value)
|
||||
val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open
|
||||
val isMe = coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false
|
||||
val secureChatAllowed = !isMe && contact.friend?.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
|
||||
val secureChatAllowed = !isMe && friend.getPresenceModelForUriOrTel(value)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
|
||||
val displayValue = if (coreContext.core.defaultAccount?.params?.domain == address.domain) (address.username ?: value) else value
|
||||
val noa = ContactNumberOrAddressData(address, hasPresence, displayValue, showSecureChat = secureChatAllowed, listener = listener)
|
||||
list.add(noa)
|
||||
}
|
||||
for (phoneNumber in contact.phoneNumbers) {
|
||||
val number = phoneNumber.value
|
||||
val presenceModel = contact.friend?.getPresenceModelForUriOrTel(number)
|
||||
|
||||
for (phoneNumber in friend.phoneNumbersWithLabel) {
|
||||
val number = phoneNumber.phoneNumber
|
||||
val presenceModel = friend.getPresenceModelForUriOrTel(number)
|
||||
val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open
|
||||
val contactAddress = presenceModel?.contact ?: number
|
||||
val address = coreContext.core.interpretUrl(contactAddress)
|
||||
address?.displayName = name
|
||||
address?.displayName = displayName.value.orEmpty()
|
||||
val isMe = if (address != null) coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false else false
|
||||
val secureChatAllowed = !isMe && contact.friend?.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
|
||||
val noa = ContactNumberOrAddressData(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, typeLabel = phoneNumber.typeLabel, listener = listener)
|
||||
val secureChatAllowed = !isMe && friend.getPresenceModelForUriOrTel(number)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
|
||||
val noa = ContactNumberOrAddressData(address, hasPresence, number, isSip = false, showSecureChat = secureChatAllowed, typeLabel = phoneNumber.label ?: "", listener = listener)
|
||||
list.add(noa)
|
||||
}
|
||||
numbersAndAddresses.value = list
|
||||
numbersAndAddresses.postValue(list)
|
||||
}
|
||||
|
||||
fun hasPresence(): Boolean {
|
||||
return contact.value?.hasPresence() ?: false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,15 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlinx.coroutines.*
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.ContactsUpdatedListenerStub
|
||||
import org.linphone.contact.NativeContact
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ContactsListViewModel : ViewModel() {
|
||||
val sipContactsSelected = MutableLiveData<Boolean>()
|
||||
|
@ -60,9 +60,11 @@ class ContactsListViewModel : ViewModel() {
|
|||
|
||||
private val magicSearchListener = object : MagicSearchListenerStub() {
|
||||
override fun onSearchResultsReceived(magicSearch: MagicSearch) {
|
||||
Log.i("[Contacts Loader] Magic search contacts available")
|
||||
searchResultsPending = false
|
||||
processMagicSearchResults(magicSearch.lastSearch)
|
||||
fetchInProgress.value = false
|
||||
// Use coreContext.contactsManager.fetchInProgress instead of false in case contacts are still being loaded
|
||||
fetchInProgress.value = coreContext.contactsManager.fetchInProgress.value
|
||||
}
|
||||
|
||||
override fun onLdapHaveMoreResults(magicSearch: MagicSearch, ldap: Ldap) {
|
||||
|
@ -72,7 +74,6 @@ class ContactsListViewModel : ViewModel() {
|
|||
|
||||
init {
|
||||
sipContactsSelected.value = coreContext.contactsManager.shouldDisplaySipContactsList()
|
||||
fetchInProgress.value = false
|
||||
|
||||
coreContext.contactsManager.addListener(contactsUpdatedListener)
|
||||
coreContext.contactsManager.magicSearch.addListener(magicSearchListener)
|
||||
|
@ -104,74 +105,81 @@ class ContactsListViewModel : ViewModel() {
|
|||
val filter = MagicSearchSource.Friends.toInt() or MagicSearchSource.LdapServers.toInt()
|
||||
searchResultsPending = true
|
||||
fastFetchJob?.cancel()
|
||||
Log.i("[Contacts Loader] Asking Magic search for contacts matching filter [$filterValue], domain [$domain] and in sources [$filter]")
|
||||
coreContext.contactsManager.magicSearch.getContactsAsync(filterValue, domain, filter)
|
||||
|
||||
val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong()
|
||||
fastFetchJob = viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(spinnerDelay)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (searchResultsPending) {
|
||||
fetchInProgress.value = true
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (searchResultsPending) {
|
||||
fetchInProgress.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processMagicSearchResults(results: Array<SearchResult>) {
|
||||
Log.i("[Contacts] Processing ${results.size} results")
|
||||
Log.i("[Contacts Loader] Processing ${results.size} results")
|
||||
contactsList.value.orEmpty().forEach(ContactViewModel::destroy)
|
||||
|
||||
val list = arrayListOf<ContactViewModel>()
|
||||
for (result in results) {
|
||||
val contact = searchMatchingContact(result) ?: Contact(searchResult = result)
|
||||
if (contact is NativeContact) {
|
||||
val found = list.find { contactViewModel ->
|
||||
contactViewModel.contactInternal is NativeContact && contactViewModel.contactInternal.nativeId == contact.nativeId
|
||||
}
|
||||
if (found != null) {
|
||||
Log.d("[Contacts] Found a search result that matches a native contact [$contact] we already have, skipping")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
val found = list.find { contactViewModel ->
|
||||
contactViewModel.displayName.value == contact.fullName
|
||||
}
|
||||
if (found != null) {
|
||||
Log.i("[Contacts] Found a search result that matches a contact [$contact] we already have, updating it with the new information")
|
||||
found.contactInternal.addAddressAndPhoneNumberFromSearchResult(result)
|
||||
found.updateNumbersAndAddresses(found.contactInternal)
|
||||
continue
|
||||
}
|
||||
}
|
||||
list.add(ContactViewModel(contact))
|
||||
}
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val list = arrayListOf<ContactViewModel>()
|
||||
val viewModels = HashMap<String, ContactViewModel>()
|
||||
|
||||
contactsList.postValue(list)
|
||||
for (result in results) {
|
||||
val friend = result.friend
|
||||
val name = friend?.name ?: LinphoneUtils.getDisplayName(result.address)
|
||||
val found = viewModels[name]
|
||||
if (found != null && friend != null) {
|
||||
continue
|
||||
}
|
||||
|
||||
val viewModel = if (friend != null) {
|
||||
ContactViewModel(friend, true)
|
||||
} else {
|
||||
val fakeFriend = coreContext.contactsManager.createFriendFromSearchResult(result)
|
||||
ContactViewModel(fakeFriend, true)
|
||||
}
|
||||
|
||||
list.add(viewModel)
|
||||
if (found == null) {
|
||||
viewModels[name] = viewModel
|
||||
}
|
||||
}
|
||||
|
||||
contactsList.postValue(list)
|
||||
viewModels.clear()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Log.i("[Contacts Loader] Processed ${results.size} results")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteContact(contact: Contact?) {
|
||||
contact ?: return
|
||||
fun deleteContact(friend: Friend) {
|
||||
friend.remove() // TODO: FIXME: friend is const here!
|
||||
|
||||
val id = friend.refKey
|
||||
if (id == null) {
|
||||
Log.w("[Contacts] Friend has no refkey, can't delete it from native address book")
|
||||
return
|
||||
}
|
||||
|
||||
val select = ContactsContract.Data.CONTACT_ID + " = ?"
|
||||
val ops = ArrayList<ContentProviderOperation>()
|
||||
|
||||
if (contact is NativeContact) {
|
||||
val nativeContact: NativeContact = contact
|
||||
Log.i("[Contacts] Adding Android contact id ${nativeContact.nativeId} to batch removal")
|
||||
val args = arrayOf(nativeContact.nativeId)
|
||||
ops.add(
|
||||
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withSelection(select, args)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (contact.friend != null) {
|
||||
Log.i("[Contacts] Removing friend")
|
||||
contact.friend?.remove()
|
||||
}
|
||||
Log.i("[Contacts] Adding Android contact id $id to batch removal")
|
||||
val args = arrayOf(id)
|
||||
ops.add(
|
||||
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withSelection(select, args)
|
||||
.build()
|
||||
)
|
||||
|
||||
if (ops.isNotEmpty()) {
|
||||
try {
|
||||
|
@ -183,26 +191,22 @@ class ContactsListViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun deleteContacts(list: ArrayList<Contact>) {
|
||||
fun deleteContacts(list: ArrayList<Friend>) {
|
||||
val select = ContactsContract.Data.CONTACT_ID + " = ?"
|
||||
val ops = ArrayList<ContentProviderOperation>()
|
||||
|
||||
for (contact in list) {
|
||||
if (contact is NativeContact) {
|
||||
val nativeContact: NativeContact = contact
|
||||
Log.i("[Contacts] Adding Android contact id ${nativeContact.nativeId} to batch removal")
|
||||
val args = arrayOf(nativeContact.nativeId)
|
||||
for (friend in list) {
|
||||
val id = friend.refKey
|
||||
if (id != null) {
|
||||
Log.i("[Contacts] Adding Android contact id $id to batch removal")
|
||||
val args = arrayOf(id)
|
||||
ops.add(
|
||||
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
|
||||
.withSelection(select, args)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (contact.friend != null) {
|
||||
Log.i("[Contacts] Removing friend")
|
||||
contact.friend?.remove()
|
||||
}
|
||||
friend.remove()
|
||||
}
|
||||
|
||||
if (ops.isNotEmpty()) {
|
||||
|
@ -214,32 +218,4 @@ class ContactsListViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchMatchingContact(searchResult: SearchResult): Contact? {
|
||||
val friend = searchResult.friend
|
||||
var displayName = ""
|
||||
if (friend != null) {
|
||||
displayName = friend.name ?: ""
|
||||
val contact: Contact? = friend.userData as? Contact
|
||||
if (contact != null) return contact
|
||||
|
||||
val friendContact = coreContext.contactsManager.findContactByFriend(friend)
|
||||
if (friendContact != null) return friendContact
|
||||
}
|
||||
|
||||
val address = searchResult.address
|
||||
if (address != null) {
|
||||
if (displayName.isEmpty()) displayName = address.displayName ?: ""
|
||||
val contact = coreContext.contactsManager.findContactByAddress(address, ignoreLocalContact = true)
|
||||
if (contact != null && (displayName.isEmpty() || contact.fullName == displayName)) return contact
|
||||
}
|
||||
|
||||
val phoneNumber = searchResult.phoneNumber
|
||||
if (phoneNumber != null && address?.username != phoneNumber) {
|
||||
val contact = coreContext.contactsManager.findContactByPhoneNumber(phoneNumber)
|
||||
if (contact != null && (displayName.isEmpty() || contact.fullName == displayName)) return contact
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.linphone.activities.main.viewmodels.SharedMainViewModel
|
|||
import org.linphone.activities.navigateToContact
|
||||
import org.linphone.activities.navigateToContacts
|
||||
import org.linphone.activities.navigateToFriend
|
||||
import org.linphone.contact.NativeContact
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.databinding.HistoryDetailFragmentBinding
|
||||
import org.linphone.utils.Event
|
||||
|
@ -80,10 +79,10 @@ class DetailCallLogFragment : GenericFragment<HistoryDetailFragmentBinding>() {
|
|||
|
||||
binding.setContactClickListener {
|
||||
sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event(R.id.masterCallLogsFragment)
|
||||
val contact = viewModel.contact.value as? NativeContact
|
||||
if (contact != null) {
|
||||
Log.i("[History] Displaying contact $contact")
|
||||
navigateToContact(contact)
|
||||
val contactId = viewModel.contact.value?.refKey
|
||||
if (contactId != null) {
|
||||
Log.i("[History] Displaying contact $contactId")
|
||||
navigateToContact(contactId)
|
||||
} else {
|
||||
val copy = viewModel.callLog.remoteAddress.clone()
|
||||
copy.clean()
|
||||
|
|
|
@ -99,7 +99,7 @@ class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = fa
|
|||
|
||||
val chatAllowed = !corePreferences.disableChat
|
||||
|
||||
val secureChatAllowed = contact.value?.friend?.getPresenceModelForUriOrTel(peerSipUri)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
|
||||
val secureChatAllowed = contact.value?.getPresenceModelForUriOrTel(peerSipUri)?.hasCapability(FriendCapability.LimeX3Dh) ?: false
|
||||
|
||||
val relatedCallLogs = MutableLiveData<ArrayList<CallLogViewModel>>()
|
||||
|
||||
|
|
|
@ -109,8 +109,7 @@ class ContactsSettingsFragment : GenericSettingFragment<SettingsContactsFragment
|
|||
if (granted) {
|
||||
Log.i("[Contacts Settings] READ_CONTACTS permission granted")
|
||||
viewModel.readContactsPermissionGranted.value = true
|
||||
coreContext.contactsManager.onReadContactsPermissionGranted()
|
||||
coreContext.contactsManager.fetchContactsAsync()
|
||||
coreContext.fetchContacts()
|
||||
} else {
|
||||
Log.w("[Contacts Settings] READ_CONTACTS permission denied")
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import android.net.Uri
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.linphone.activities.main.history.data.GroupedCallLogData
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.core.*
|
||||
import org.linphone.utils.Event
|
||||
|
||||
|
@ -76,7 +75,7 @@ class SharedMainViewModel : ViewModel() {
|
|||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
val selectedContact = MutableLiveData<Contact>()
|
||||
val selectedContact = MutableLiveData<Friend>()
|
||||
|
||||
// For correct animations directions
|
||||
val updateContactsAnimationsBasedOnDestination: MutableLiveData<Event<Int>> by lazy {
|
||||
|
|
|
@ -25,7 +25,7 @@ import android.view.View
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication
|
||||
import org.linphone.R
|
||||
import org.linphone.activities.GenericFragment
|
||||
import org.linphone.activities.voip.viewmodels.ConferenceParticipantsViewModel
|
||||
|
@ -123,8 +123,7 @@ class ConferenceAddParticipantsFragment : GenericFragment<VoipConferenceParticip
|
|||
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
if (granted) {
|
||||
Log.i("[Conference Add Participants] READ_CONTACTS permission granted")
|
||||
coreContext.contactsManager.onReadContactsPermissionGranted()
|
||||
coreContext.contactsManager.fetchContactsAsync()
|
||||
LinphoneApplication.coreContext.fetchContacts()
|
||||
} else {
|
||||
Log.w("[Conference Add Participants] READ_CONTACTS permission denied")
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ class ConferenceParticipantsFragment : GenericFragment<VoipConferenceParticipant
|
|||
) {
|
||||
it.consume { participantData ->
|
||||
val participantName =
|
||||
participantData.contact.value?.fullName ?: participantData.displayName.value
|
||||
participantData.contact.value?.name ?: participantData.displayName.value
|
||||
val message = if (participantData.participant.isAdmin) {
|
||||
getString(R.string.conference_admin_set).format(participantName)
|
||||
} else {
|
||||
|
|
|
@ -38,8 +38,9 @@ import androidx.core.content.ContextCompat
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.getThumbnailUri
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.notifications.Notifiable
|
||||
import org.linphone.notifications.NotificationsManager
|
||||
|
@ -144,10 +145,9 @@ class Api26Compatibility {
|
|||
pendingIntent: PendingIntent,
|
||||
notificationsManager: NotificationsManager
|
||||
): Notification {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val pictureUri = contact?.getContactThumbnailPictureUri()
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val contact: Friend? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri())
|
||||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress)
|
||||
|
||||
val notificationLayoutHeadsUp = RemoteViews(context.packageName, R.layout.call_incoming_notification_heads_up)
|
||||
|
@ -193,10 +193,9 @@ class Api26Compatibility {
|
|||
channel: String,
|
||||
notificationsManager: NotificationsManager
|
||||
): Notification {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val pictureUri = contact?.getContactThumbnailPictureUri()
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val contact: Friend? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri())
|
||||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
|
||||
val stringResourceId: Int
|
||||
val iconResourceId: Int
|
||||
|
@ -226,7 +225,7 @@ class Api26Compatibility {
|
|||
val builder = NotificationCompat.Builder(
|
||||
context, channel
|
||||
)
|
||||
.setContentTitle(contact?.fullName ?: displayName)
|
||||
.setContentTitle(contact?.name ?: displayName)
|
||||
.setContentText(context.getString(stringResourceId))
|
||||
.setSmallIcon(iconResourceId)
|
||||
.setLargeIcon(roundPicture)
|
||||
|
|
|
@ -29,7 +29,7 @@ import androidx.core.content.ContextCompat
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.getThumbnailUri
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.notifications.Notifiable
|
||||
|
@ -51,10 +51,9 @@ class Api31Compatibility {
|
|||
pendingIntent: PendingIntent,
|
||||
notificationsManager: NotificationsManager
|
||||
): Notification {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val pictureUri = contact?.getContactThumbnailPictureUri()
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val contact = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri())
|
||||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
|
||||
val person = notificationsManager.getPerson(contact, displayName, roundPicture)
|
||||
val caller = Person.Builder()
|
||||
|
@ -99,11 +98,10 @@ class Api31Compatibility {
|
|||
channel: String,
|
||||
notificationsManager: NotificationsManager
|
||||
): Notification {
|
||||
val contact: Contact? =
|
||||
val contact =
|
||||
coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val pictureUri = contact?.getContactThumbnailPictureUri()
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri())
|
||||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
|
||||
val isVideo = call.currentParams.isVideoEnabled
|
||||
val iconResourceId: Int = when (call.state) {
|
||||
|
|
|
@ -28,7 +28,7 @@ import androidx.core.content.ContextCompat
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.getThumbnailUri
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.notifications.Notifiable
|
||||
import org.linphone.notifications.NotificationsManager
|
||||
|
@ -45,10 +45,9 @@ class XiaomiCompatibility {
|
|||
pendingIntent: PendingIntent,
|
||||
notificationsManager: NotificationsManager
|
||||
): Notification {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val pictureUri = contact?.getContactThumbnailPictureUri()
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val contact = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri())
|
||||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val address = LinphoneUtils.getDisplayableAddress(call.remoteAddress)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, context.getString(R.string.notification_channel_incoming_call_id))
|
||||
|
|
|
@ -1,260 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.contact
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.os.AsyncTask
|
||||
import android.provider.ContactsContract
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
class AsyncContactsLoader(private val context: Context) :
|
||||
AsyncTask<Void, Void, AsyncContactsLoader.AsyncContactsData>() {
|
||||
companion object {
|
||||
val projection = arrayOf(
|
||||
ContactsContract.Data.CONTACT_ID,
|
||||
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.Contacts.STARRED,
|
||||
ContactsContract.Contacts.LOOKUP_KEY,
|
||||
"data1", // Company, Phone or SIP Address
|
||||
"data2", // ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.SipAddress.TYPE
|
||||
"data3", // ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ContactsContract.CommonDataKinds.Phone.LABEL, ContactsContract.CommonDataKinds.SipAddress.LABEL
|
||||
"data4"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPreExecute() {
|
||||
if (isCancelled) return
|
||||
Log.i("[Contacts Loader] Synchronization started")
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg args: Void): AsyncContactsData {
|
||||
val data = AsyncContactsData()
|
||||
if (isCancelled) return data
|
||||
|
||||
Log.i("[Contacts Loader] Background synchronization started")
|
||||
val core: Core = coreContext.core
|
||||
val androidContactsCache: HashMap<String, Contact> = HashMap()
|
||||
val nativeIds = arrayListOf<String>()
|
||||
val friendLists = core.friendsLists
|
||||
|
||||
for (list in friendLists) {
|
||||
val friends = list.friends
|
||||
for (friend in friends) {
|
||||
if (isCancelled) {
|
||||
Log.w("[Contacts Loader] Task cancelled")
|
||||
return data
|
||||
}
|
||||
var contact: Contact? = friend.userData as? Contact
|
||||
if (contact != null) {
|
||||
if (contact is NativeContact) {
|
||||
contact.sipAddresses.clear()
|
||||
contact.rawSipAddresses.clear()
|
||||
contact.phoneNumbers.clear()
|
||||
contact.rawPhoneNumbers.clear()
|
||||
androidContactsCache[contact.nativeId] = contact
|
||||
nativeIds.add(contact.nativeId)
|
||||
} else {
|
||||
data.contacts.add(contact)
|
||||
}
|
||||
} else {
|
||||
if (friend.refKey != null) {
|
||||
// Friend has a refkey but no LinphoneContact => represents a
|
||||
// native contact stored in db from a previous version of Linphone,
|
||||
// remove it
|
||||
list.removeFriend(friend)
|
||||
} else { // No refkey so it's a standalone contact
|
||||
contact = Contact()
|
||||
contact.friend = friend
|
||||
contact.syncValuesFromFriend()
|
||||
friend.userData = contact
|
||||
data.contacts.add(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PermissionHelper.required(context).hasReadContactsPermission()) {
|
||||
var selection: String? = null
|
||||
if (corePreferences.fetchContactsFromDefaultDirectory) {
|
||||
Log.i("[Contacts Loader] Only fetching contacts in default directory")
|
||||
selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1"
|
||||
}
|
||||
|
||||
val cursor: Cursor? = try {
|
||||
context.contentResolver
|
||||
.query(
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("[Contacts Loader] Failed to get contacts cursor: $e")
|
||||
null
|
||||
}
|
||||
|
||||
if (cursor != null) {
|
||||
Log.i("[Contacts Loader] Found ${cursor.count} entries in cursor")
|
||||
while (cursor.moveToNext()) {
|
||||
if (isCancelled) {
|
||||
Log.w("[Contacts Loader] Task cancelled")
|
||||
cursor.close()
|
||||
return data
|
||||
}
|
||||
|
||||
try {
|
||||
val id: String =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID))
|
||||
val starred =
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.STARRED)) == 1
|
||||
val lookupKey = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.LOOKUP_KEY))
|
||||
var contact: Contact? = androidContactsCache[id]
|
||||
if (contact == null) {
|
||||
Log.d(
|
||||
"[Contacts Loader] Creating contact with native ID $id, favorite flag is $starred"
|
||||
)
|
||||
nativeIds.add(id)
|
||||
contact = NativeContact(id, lookupKey)
|
||||
contact.isStarred = starred
|
||||
androidContactsCache[id] = contact
|
||||
}
|
||||
contact.syncValuesFromAndroidCursor(cursor)
|
||||
} catch (ise: IllegalStateException) {
|
||||
Log.e(
|
||||
"[Contacts Loader] Couldn't get values from cursor, exception: $ise"
|
||||
)
|
||||
} catch (iae: IllegalArgumentException) {
|
||||
Log.e(
|
||||
"[Contacts Loader] Couldn't get values from cursor, exception: $iae"
|
||||
)
|
||||
}
|
||||
}
|
||||
cursor.close()
|
||||
} else {
|
||||
Log.w("[Contacts Loader] Read contacts permission denied, can't fetch native contacts")
|
||||
}
|
||||
|
||||
for (list in core.friendsLists) {
|
||||
val friends = list.friends
|
||||
for (friend in friends) {
|
||||
if (isCancelled) {
|
||||
Log.w("[Contacts Loader] Task cancelled")
|
||||
return data
|
||||
}
|
||||
val contact: Contact? = friend.userData as? Contact
|
||||
if (contact != null && contact is NativeContact) {
|
||||
if (!nativeIds.contains(contact.nativeId)) {
|
||||
Log.i("[Contacts Loader] Contact removed since last fetch: ${contact.nativeId}")
|
||||
// Has been removed since last fetch
|
||||
androidContactsCache.remove(contact.nativeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nativeIds.clear()
|
||||
}
|
||||
|
||||
val contacts: Collection<Contact> = androidContactsCache.values
|
||||
// New friends count will be 0 after the first contacts fetch
|
||||
Log.i(
|
||||
"[Contacts Loader] Found ${contacts.size} native contacts plus ${data.contacts.size} friends in the configuration file"
|
||||
)
|
||||
for (contact in contacts) {
|
||||
if (isCancelled) {
|
||||
Log.w("[Contacts Loader] Task cancelled")
|
||||
return data
|
||||
}
|
||||
if (contact.sipAddresses.isEmpty() && contact.phoneNumbers.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (contact.fullName == null) {
|
||||
for (address in contact.sipAddresses) {
|
||||
contact.fullName = LinphoneUtils.getDisplayName(address)
|
||||
Log.w(
|
||||
"[Contacts Loader] Couldn't find a display name for contact ${contact.fullName}, used SIP address display name / username instead..."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data.contacts.add(contact)
|
||||
}
|
||||
androidContactsCache.clear()
|
||||
|
||||
data.contacts.sort()
|
||||
|
||||
Log.i("[Contacts Loader] Background synchronization finished")
|
||||
return data
|
||||
}
|
||||
|
||||
override fun onPostExecute(data: AsyncContactsData) {
|
||||
if (isCancelled) return
|
||||
Log.i("[Contacts Loader] ${data.contacts.size} contacts")
|
||||
|
||||
for (contact in data.contacts) {
|
||||
if (contact is NativeContact) {
|
||||
contact.createOrUpdateFriendFromNativeContact()
|
||||
}
|
||||
}
|
||||
|
||||
// Now that contact fetching is asynchronous, this is required to ensure
|
||||
// presence subscription event will be sent with all friends
|
||||
val core = coreContext.core
|
||||
if (core.isFriendListSubscriptionEnabled) {
|
||||
Log.i("[Contacts Loader] Matching friends created, updating subscription")
|
||||
for (list in core.friendsLists) {
|
||||
if (list.rlsAddress == null) {
|
||||
Log.w("[Contacts Loader] Friend list subscription enabled but RLS URI not set!")
|
||||
val defaultRlsUri = corePreferences.defaultRlsUri
|
||||
if (defaultRlsUri.isNotEmpty()) {
|
||||
val rlsAddress = core.interpretUrl(defaultRlsUri)
|
||||
if (rlsAddress != null) {
|
||||
Log.i("[Contacts Loader] Using new RLS URI: ${rlsAddress.asStringUriOnly()}")
|
||||
list.rlsAddress = rlsAddress
|
||||
} else {
|
||||
Log.e("[Contacts Loader] Couldn't parse RLS URI: $defaultRlsUri")
|
||||
}
|
||||
} else {
|
||||
Log.e("[Contacts Loader] RLS URI not found in config file!")
|
||||
}
|
||||
}
|
||||
|
||||
list.updateSubscriptions()
|
||||
}
|
||||
}
|
||||
|
||||
coreContext.contactsManager.updateContacts(data.contacts)
|
||||
|
||||
Log.i("[Contacts Loader] Synchronization finished")
|
||||
}
|
||||
|
||||
class AsyncContactsData {
|
||||
val contacts = arrayListOf<Contact>()
|
||||
}
|
||||
}
|
|
@ -58,16 +58,17 @@ class BigContactAvatarView : LinearLayout {
|
|||
}
|
||||
binding.root.visibility = View.VISIBLE
|
||||
|
||||
val contact: Contact? = viewModel.contact.value
|
||||
val contact = viewModel.contact.value
|
||||
val initials = if (contact != null) {
|
||||
AppUtils.getInitials(contact.fullName ?: contact.firstName + " " + contact.lastName)
|
||||
AppUtils.getInitials(contact.name ?: "")
|
||||
} else {
|
||||
AppUtils.getInitials(viewModel.displayName.value ?: "")
|
||||
}
|
||||
|
||||
binding.initials = initials
|
||||
binding.generatedAvatarVisibility = initials.isNotEmpty() && initials != "+"
|
||||
binding.imagePath = contact?.getContactPictureUri()
|
||||
binding.imagePath = contact?.getPictureUri()
|
||||
binding.thumbnailPath = contact?.getThumbnailUri()
|
||||
binding.borderVisibility = corePreferences.showBorderOnBigContactAvatar
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.contact
|
||||
|
||||
import android.database.Cursor
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.PresenceBasicStatus
|
||||
import org.linphone.core.SearchResult
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.ImageUtils
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
data class PhoneNumber(val value: String, val typeLabel: String) : Comparable<PhoneNumber> {
|
||||
override fun compareTo(other: PhoneNumber): Int {
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
}
|
||||
|
||||
open class Contact() : Comparable<Contact> {
|
||||
var fullName: String? = null
|
||||
var firstName: String? = null
|
||||
var lastName: String? = null
|
||||
var organization: String? = null
|
||||
var isStarred: Boolean = false
|
||||
|
||||
var phoneNumbers = arrayListOf<PhoneNumber>()
|
||||
var rawPhoneNumbers = arrayListOf<String>()
|
||||
var sipAddresses = arrayListOf<Address>()
|
||||
// Raw SIP addresses are only used for contact edition
|
||||
var rawSipAddresses = arrayListOf<String>()
|
||||
|
||||
var friend: Friend? = null
|
||||
|
||||
private var thumbnailUri: Uri? = null
|
||||
|
||||
constructor(searchResult: SearchResult) : this() {
|
||||
friend = searchResult.friend
|
||||
fullName = friend?.name
|
||||
addAddressAndPhoneNumberFromSearchResult(searchResult)
|
||||
}
|
||||
|
||||
fun addAddressAndPhoneNumberFromSearchResult(searchResult: SearchResult) {
|
||||
val address = searchResult.address
|
||||
if (address != null) {
|
||||
if (fullName == null) {
|
||||
fullName = friend?.name ?: LinphoneUtils.getDisplayName(address)
|
||||
}
|
||||
|
||||
sipAddresses.add(address)
|
||||
}
|
||||
|
||||
val phoneNumber = searchResult.phoneNumber
|
||||
if (phoneNumber != null) {
|
||||
if (address == null && fullName == null) {
|
||||
fullName = friend?.name ?: phoneNumber.orEmpty()
|
||||
}
|
||||
|
||||
if (address != null && address.username == phoneNumber) {
|
||||
sipAddresses.remove(address)
|
||||
}
|
||||
phoneNumbers.add(PhoneNumber(phoneNumber, ""))
|
||||
}
|
||||
}
|
||||
|
||||
override fun compareTo(other: Contact): Int {
|
||||
val fn = fullName ?: ""
|
||||
val otherFn = other.fullName ?: ""
|
||||
|
||||
if (fn == otherFn) {
|
||||
if (phoneNumbers.size == other.phoneNumbers.size && phoneNumbers.size > 0) {
|
||||
if (phoneNumbers != other.phoneNumbers) {
|
||||
for (i in 0 until phoneNumbers.size) {
|
||||
val compare = phoneNumbers[i].compareTo(other.phoneNumbers[i])
|
||||
if (compare != 0) return compare
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return phoneNumbers.size.compareTo(other.phoneNumbers.size)
|
||||
}
|
||||
|
||||
if (sipAddresses.size == other.sipAddresses.size && sipAddresses.size > 0) {
|
||||
if (sipAddresses != other.sipAddresses) {
|
||||
for (i in 0 until sipAddresses.size) {
|
||||
val compare = sipAddresses[i].asStringUriOnly().compareTo(other.sipAddresses[i].asStringUriOnly())
|
||||
if (compare != 0) return compare
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return sipAddresses.size.compareTo(other.sipAddresses.size)
|
||||
}
|
||||
|
||||
val org = organization ?: ""
|
||||
val otherOrg = other.organization ?: ""
|
||||
return org.compareTo(otherOrg)
|
||||
}
|
||||
|
||||
return coreContext.collator.compare(fn, otherFn)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun syncValuesFromFriend() {
|
||||
val friend = this.friend
|
||||
friend ?: return
|
||||
|
||||
phoneNumbers.clear()
|
||||
for (number in friend.phoneNumbers) {
|
||||
if (!rawPhoneNumbers.contains(number)) {
|
||||
phoneNumbers.add(PhoneNumber(number, ""))
|
||||
rawPhoneNumbers.add(number)
|
||||
}
|
||||
}
|
||||
|
||||
sipAddresses.clear()
|
||||
rawSipAddresses.clear()
|
||||
for (address in friend.addresses) {
|
||||
val stringAddress = address.asStringUriOnly()
|
||||
if (!rawSipAddresses.contains(stringAddress)) {
|
||||
sipAddresses.add(address)
|
||||
rawSipAddresses.add(stringAddress)
|
||||
}
|
||||
}
|
||||
|
||||
fullName = friend.name
|
||||
val vCard = friend.vcard
|
||||
if (vCard != null) {
|
||||
lastName = vCard.familyName
|
||||
firstName = vCard.givenName
|
||||
organization = vCard.organization
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
open fun syncValuesFromAndroidCursor(cursor: Cursor) {
|
||||
Log.e("[Contact] Not a native contact, skip")
|
||||
}
|
||||
|
||||
open fun getContactThumbnailPictureUri(): Uri? {
|
||||
return thumbnailUri
|
||||
}
|
||||
|
||||
fun setContactThumbnailPictureUri(uri: Uri) {
|
||||
thumbnailUri = uri
|
||||
}
|
||||
|
||||
open fun getContactPictureUri(): Uri? {
|
||||
return thumbnailUri
|
||||
}
|
||||
|
||||
open fun getPerson(): Person {
|
||||
val personBuilder = Person.Builder().setName(fullName)
|
||||
|
||||
val bm: Bitmap? =
|
||||
ImageUtils.getRoundBitmapFromUri(
|
||||
coreContext.context,
|
||||
getContactThumbnailPictureUri()
|
||||
)
|
||||
val icon =
|
||||
if (bm == null) IconCompat.createWithResource(
|
||||
coreContext.context,
|
||||
R.drawable.voip_single_contact_avatar_alt
|
||||
) else IconCompat.createWithAdaptiveBitmap(bm)
|
||||
if (icon != null) {
|
||||
personBuilder.setIcon(icon)
|
||||
}
|
||||
|
||||
personBuilder.setImportant(isStarred)
|
||||
return personBuilder.build()
|
||||
}
|
||||
|
||||
fun hasPresence(): Boolean {
|
||||
if (friend == null) return false
|
||||
for (address in sipAddresses) {
|
||||
val presenceModel = friend?.getPresenceModelForUriOrTel(address.asStringUriOnly())
|
||||
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return true
|
||||
}
|
||||
for (number in rawPhoneNumbers) {
|
||||
val presenceModel = friend?.getPresenceModelForUriOrTel(number)
|
||||
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getContactForPhoneNumberOrAddress(value: String): String? {
|
||||
val presenceModel = friend?.getPresenceModelForUriOrTel(value)
|
||||
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact
|
||||
return null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${super.toString()}: name [$fullName]"
|
||||
}
|
||||
}
|
|
@ -52,9 +52,9 @@ class ContactAvatarView : LinearLayout {
|
|||
}
|
||||
|
||||
fun setData(data: ContactDataInterface) {
|
||||
val contact: Contact? = data.contact.value
|
||||
val contact = data.contact.value
|
||||
val initials = if (contact != null) {
|
||||
AppUtils.getInitials(contact.fullName ?: contact.firstName + " " + contact.lastName)
|
||||
AppUtils.getInitials(contact.name ?: "")
|
||||
} else {
|
||||
AppUtils.getInitials(data.displayName.value ?: "")
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class ContactAvatarView : LinearLayout {
|
|||
binding.generatedAvatarVisibility = initials.isNotEmpty() && initials != "+"
|
||||
binding.groupChatAvatarVisibility = data.showGroupChatAvatar
|
||||
|
||||
binding.imagePath = contact?.getContactThumbnailPictureUri()
|
||||
binding.imagePath = contact?.getThumbnailUri()
|
||||
binding.borderVisibility = corePreferences.showBorderOnContactAvatar
|
||||
|
||||
binding.securityIcon = when (data.securityLevel.value) {
|
||||
|
|
|
@ -19,16 +19,18 @@
|
|||
*/
|
||||
package org.linphone.contact
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.activities.main.viewmodels.MessageNotifierViewModel
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.ChatRoomSecurityLevel
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
interface ContactDataInterface {
|
||||
val contact: MutableLiveData<Contact>
|
||||
val contact: MutableLiveData<Friend>
|
||||
|
||||
val displayName: MutableLiveData<String>
|
||||
|
||||
|
@ -39,14 +41,21 @@ interface ContactDataInterface {
|
|||
}
|
||||
|
||||
open class GenericContactData(private val sipAddress: Address) : ContactDataInterface {
|
||||
final override val contact: MutableLiveData<Contact> = MutableLiveData<Contact>()
|
||||
final override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||
final override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||
final override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||
|
||||
val initials = MutableLiveData<String>()
|
||||
val displayInitials = MutableLiveData<Boolean>()
|
||||
|
||||
val thumbnailUri: Uri?
|
||||
get() = contact.value?.getThumbnailUri()
|
||||
|
||||
val pictureUri: Uri?
|
||||
get() = contact.value?.getPictureUri()
|
||||
|
||||
private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() {
|
||||
override fun onContactUpdated(contact: Contact) {
|
||||
override fun onContactUpdated(friend: Friend) {
|
||||
contactLookup()
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +77,7 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte
|
|||
contact.value = c
|
||||
|
||||
initials.value = if (c != null) {
|
||||
AppUtils.getInitials(c.fullName ?: c.firstName + " " + c.lastName)
|
||||
AppUtils.getInitials(c.name ?: "")
|
||||
} else {
|
||||
AppUtils.getInitials(displayName.value ?: "")
|
||||
}
|
||||
|
@ -77,12 +86,18 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte
|
|||
}
|
||||
|
||||
abstract class GenericContactViewModel(private val sipAddress: Address) : MessageNotifierViewModel(), ContactDataInterface {
|
||||
final override val contact: MutableLiveData<Contact> = MutableLiveData<Contact>()
|
||||
final override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||
final override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||
final override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||
|
||||
val thumbnailUri: Uri?
|
||||
get() = contact.value?.getThumbnailUri()
|
||||
|
||||
val pictureUri: Uri?
|
||||
get() = contact.value?.getPictureUri()
|
||||
|
||||
private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() {
|
||||
override fun onContactUpdated(contact: Contact) {
|
||||
override fun onContactUpdated(friend: Friend) {
|
||||
contactLookup()
|
||||
}
|
||||
}
|
||||
|
|
210
app/src/main/java/org/linphone/contact/ContactLoader.kt
Normal file
210
app/src/main/java/org/linphone/contact/ContactLoader.kt
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2021 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.contact
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
import android.util.Patterns
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.CursorLoader
|
||||
import androidx.loader.content.Loader
|
||||
import java.lang.Exception
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Factory
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ContactLoader : LoaderManager.LoaderCallbacks<Cursor> {
|
||||
companion object {
|
||||
val projection = arrayOf(
|
||||
ContactsContract.Data.CONTACT_ID,
|
||||
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
|
||||
ContactsContract.Data.MIMETYPE,
|
||||
ContactsContract.Contacts.STARRED,
|
||||
ContactsContract.Contacts.LOOKUP_KEY,
|
||||
"data1", // Company, Phone or SIP Address
|
||||
"data2", // ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.SipAddress.TYPE
|
||||
"data3", // ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ContactsContract.CommonDataKinds.Phone.LABEL, ContactsContract.CommonDataKinds.SipAddress.LABEL
|
||||
"data4"
|
||||
)
|
||||
}
|
||||
|
||||
private val friends = HashMap<String, Friend>()
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
|
||||
Log.i("[Contacts Loader] Loader created")
|
||||
coreContext.contactsManager.fetchInProgress.value = true
|
||||
return CursorLoader(
|
||||
coreContext.context,
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
projection,
|
||||
ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1",
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
|
||||
Log.i("[Contacts Loader] Load finished, found ${cursor.count} entries in cursor")
|
||||
|
||||
friends.clear()
|
||||
val core = coreContext.core
|
||||
val linphoneMime = loader.context.getString(R.string.linphone_address_mime_type)
|
||||
|
||||
coreContext.lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
while (!cursor.isClosed && cursor.moveToNext()) {
|
||||
try {
|
||||
val id: String =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID))
|
||||
val displayName: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.DISPLAY_NAME_PRIMARY))
|
||||
val mime: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE))
|
||||
val data1: String? = cursor.getString(cursor.getColumnIndexOrThrow("data1"))
|
||||
val data2: String? = cursor.getString(cursor.getColumnIndexOrThrow("data2"))
|
||||
val data3: String? = cursor.getString(cursor.getColumnIndexOrThrow("data3"))
|
||||
val data4: String? = cursor.getString(cursor.getColumnIndexOrThrow("data4"))
|
||||
val starred =
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.STARRED)) == 1
|
||||
val lookupKey =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.LOOKUP_KEY))
|
||||
|
||||
val friend = friends[id] ?: core.createFriend()
|
||||
friend.refKey = id
|
||||
if (friend.name.isNullOrEmpty()) {
|
||||
friend.name = displayName
|
||||
friend.photo = Uri.withAppendedPath(
|
||||
ContentUris.withAppendedId(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
id.toLong()
|
||||
),
|
||||
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
|
||||
).toString()
|
||||
friend.starred = starred
|
||||
friend.nativeUri =
|
||||
"${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey"
|
||||
}
|
||||
|
||||
when (mime) {
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
|
||||
val typeLabel = ContactsContract.CommonDataKinds.Phone.getTypeLabel(
|
||||
loader.context.resources,
|
||||
data2?.toInt() ?: 0,
|
||||
data3
|
||||
).toString()
|
||||
|
||||
val number =
|
||||
if (corePreferences.preferNormalizedPhoneNumbersFromAddressBook ||
|
||||
data1.isNullOrEmpty() ||
|
||||
!Patterns.PHONE.matcher(data1).matches()
|
||||
) {
|
||||
data4 ?: data1
|
||||
} else {
|
||||
data1
|
||||
}
|
||||
if (number != null) {
|
||||
var duplicate = false
|
||||
for (pn in friend.phoneNumbersWithLabel) {
|
||||
if (pn.label == typeLabel && LinphoneUtils.arePhoneNumberWeakEqual(
|
||||
pn.phoneNumber,
|
||||
number
|
||||
)
|
||||
) {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
val phoneNumber = Factory.instance()
|
||||
.createFriendPhoneNumber(number, typeLabel)
|
||||
friend.addPhoneNumberWithLabel(phoneNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
linphoneMime, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
|
||||
if (data1 == null) continue
|
||||
val address = core.interpretUrl(data1) ?: continue
|
||||
friend.addAddress(address)
|
||||
}
|
||||
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> {
|
||||
if (data1 == null) continue
|
||||
val vCard = friend.vcard
|
||||
vCard?.organization = data1
|
||||
}
|
||||
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
|
||||
if (data2 == null && data3 == null) continue
|
||||
val vCard = friend.vcard
|
||||
vCard?.givenName = data2
|
||||
vCard?.familyName = data3
|
||||
}
|
||||
}
|
||||
|
||||
friends[id] = friend
|
||||
} catch (e: Exception) {
|
||||
Log.e("[Contacts Loader] Exception: $e")
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Log.i("[Contacts Loader] Friends created")
|
||||
val contactId = coreContext.contactsManager.contactIdToWatchFor
|
||||
if (contactId.isNotEmpty()) {
|
||||
val friend = friends[contactId]
|
||||
Log.i("[Contacts Loader] Manager was asked to monitor contact id $contactId")
|
||||
if (friend != null) {
|
||||
Log.i("[Contacts Loader] Found new contact matching id $contactId, notifying listeners")
|
||||
coreContext.contactsManager.notifyListeners(friend)
|
||||
}
|
||||
}
|
||||
|
||||
val fl = core.defaultFriendList ?: core.createFriendList()
|
||||
for (friend in fl.friends) {
|
||||
fl.removeFriend(friend)
|
||||
}
|
||||
|
||||
if (fl != core.defaultFriendList) core.addFriendList(fl)
|
||||
for (friend in friends.values) {
|
||||
fl.addLocalFriend(friend)
|
||||
}
|
||||
fl.updateSubscriptions()
|
||||
|
||||
Log.i("[Contacts Loader] Friends added & subscription updated")
|
||||
friends.clear()
|
||||
coreContext.contactsManager.fetchFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoaderReset(loader: Loader<Cursor>) {
|
||||
Log.i("[Contacts Loader] Loader reset")
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ import org.linphone.core.*
|
|||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class ContactSelectionData(private val searchResult: SearchResult) : ContactDataInterface {
|
||||
override val contact: MutableLiveData<Contact> = MutableLiveData<Contact>()
|
||||
override val contact: MutableLiveData<Friend> = MutableLiveData<Friend>()
|
||||
override val displayName: MutableLiveData<String> = MutableLiveData<String>()
|
||||
override val securityLevel: MutableLiveData<ChatRoomSecurityLevel> = MutableLiveData<ChatRoomSecurityLevel>()
|
||||
|
||||
|
|
|
@ -23,57 +23,39 @@ import android.accounts.Account
|
|||
import android.accounts.AccountManager
|
||||
import android.accounts.AuthenticatorDescription
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.AsyncTask.THREAD_POOL_EXECUTOR
|
||||
import android.provider.ContactsContract
|
||||
import android.util.Patterns
|
||||
import java.io.File
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.lang.NumberFormatException
|
||||
import kotlinx.coroutines.*
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.ImageUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
interface ContactsUpdatedListener {
|
||||
fun onContactsUpdated()
|
||||
|
||||
fun onContactUpdated(contact: Contact)
|
||||
fun onContactUpdated(friend: Friend)
|
||||
}
|
||||
|
||||
open class ContactsUpdatedListenerStub : ContactsUpdatedListener {
|
||||
override fun onContactsUpdated() {}
|
||||
|
||||
override fun onContactUpdated(contact: Contact) {}
|
||||
override fun onContactUpdated(friend: Friend) {}
|
||||
}
|
||||
|
||||
class ContactsManager(private val context: Context) {
|
||||
private val contactsObserver: ContentObserver by lazy {
|
||||
object : ContentObserver(coreContext.handler) {
|
||||
@Synchronized
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
onChange(selfChange, null)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||
Log.i("[Contacts Observer] At least one contact has changed")
|
||||
fetchContactsAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var contacts = ArrayList<Contact>()
|
||||
@Synchronized
|
||||
get
|
||||
@Synchronized
|
||||
private set
|
||||
|
||||
val magicSearch: MagicSearch by lazy {
|
||||
val magicSearch = coreContext.core.createMagicSearch()
|
||||
magicSearch.limitedSearch = false
|
||||
|
@ -82,18 +64,14 @@ class ContactsManager(private val context: Context) {
|
|||
|
||||
var latestContactFetch: String = ""
|
||||
|
||||
private var localAccountsContacts = ArrayList<Contact>()
|
||||
@Synchronized
|
||||
get
|
||||
@Synchronized
|
||||
private set
|
||||
val fetchInProgress = MutableLiveData<Boolean>()
|
||||
|
||||
private val friendsMap: HashMap<String, Contact> = HashMap()
|
||||
var contactIdToWatchFor: String = ""
|
||||
|
||||
private val localFriends = arrayListOf<Friend>()
|
||||
|
||||
private val contactsUpdatedListeners = ArrayList<ContactsUpdatedListener>()
|
||||
|
||||
private var loadContactsTask: AsyncContactsLoader? = null
|
||||
|
||||
private val friendListListener: FriendListListenerStub = object : FriendListListenerStub() {
|
||||
@Synchronized
|
||||
override fun onPresenceReceived(list: FriendList, friends: Array<Friend>) {
|
||||
|
@ -106,10 +84,6 @@ class ContactsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
init {
|
||||
if (PermissionHelper.required(context).hasReadContactsPermission()) {
|
||||
onReadContactsPermissionGranted()
|
||||
}
|
||||
|
||||
initSyncAccount()
|
||||
|
||||
val core = coreContext.core
|
||||
|
@ -119,75 +93,42 @@ class ContactsManager(private val context: Context) {
|
|||
Log.i("[Contacts Manager] Created")
|
||||
}
|
||||
|
||||
fun onReadContactsPermissionGranted() {
|
||||
Log.i("[Contacts Manager] Register contacts observer")
|
||||
context.contentResolver.registerContentObserver(
|
||||
ContactsContract.Contacts.CONTENT_URI,
|
||||
true,
|
||||
contactsObserver
|
||||
)
|
||||
}
|
||||
|
||||
fun shouldDisplaySipContactsList(): Boolean {
|
||||
return coreContext.core.defaultAccount?.params?.identityAddress?.domain == corePreferences.defaultDomain
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun fetchContactsAsync() {
|
||||
fun fetchFinished() {
|
||||
Log.i("[Contacts Manager] Contacts loader have finished")
|
||||
latestContactFetch = System.currentTimeMillis().toString()
|
||||
|
||||
if (loadContactsTask != null) {
|
||||
Log.w("[Contacts Manager] Cancelling existing async task")
|
||||
loadContactsTask?.cancel(true)
|
||||
}
|
||||
loadContactsTask = AsyncContactsLoader(context)
|
||||
loadContactsTask?.executeOnExecutor(THREAD_POOL_EXECUTOR)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addContact(contact: Contact) {
|
||||
contacts.add(contact)
|
||||
updateLocalContacts()
|
||||
fetchInProgress.value = false
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateLocalContacts() {
|
||||
Log.i("[Contacts Manager] Updating local contact(s)")
|
||||
localAccountsContacts.clear()
|
||||
localFriends.clear()
|
||||
|
||||
for (account in coreContext.core.accountList) {
|
||||
val localContact = Contact()
|
||||
localContact.fullName = account.params.identityAddress?.displayName ?: account.params.identityAddress?.username
|
||||
val friend = coreContext.core.createFriend()
|
||||
friend.name = account.params.identityAddress?.displayName ?: account.params.identityAddress?.username
|
||||
|
||||
val address = account.params.identityAddress ?: continue
|
||||
friend.address = address
|
||||
|
||||
val pictureUri = corePreferences.defaultAccountAvatarPath
|
||||
if (pictureUri != null) {
|
||||
Log.i("[Contacts Manager] Found local picture URI: $pictureUri")
|
||||
localContact.setContactThumbnailPictureUri(Uri.fromFile(File(pictureUri)))
|
||||
val parsedUri = if (pictureUri.startsWith("/")) "file:$pictureUri" else pictureUri
|
||||
Log.i("[Contacts Manager] Found local picture URI: $parsedUri")
|
||||
friend.photo = parsedUri
|
||||
}
|
||||
val address = account.params.identityAddress
|
||||
if (address != null) {
|
||||
localContact.sipAddresses.add(address)
|
||||
localContact.rawSipAddresses.add(address.asStringUriOnly())
|
||||
}
|
||||
localAccountsContacts.add(localContact)
|
||||
|
||||
Log.i("[Contacts Manager] Local contact created for account [${address.asString()}] and picture [${friend.photo}]")
|
||||
localFriends.add(friend)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateContacts(all: ArrayList<Contact>) {
|
||||
friendsMap.clear()
|
||||
// Contact has a Friend field and Friend can have a Contact has userData
|
||||
// Friend also keeps a ref on the Core, so we have to clean them
|
||||
for (contact in contacts) {
|
||||
contact.friend = null
|
||||
}
|
||||
contacts.clear()
|
||||
contacts.addAll(all)
|
||||
|
||||
updateLocalContacts()
|
||||
|
||||
Log.i("[Contacts Manager] Async fetching finished, notifying observers")
|
||||
notifyListeners()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getAndroidContactIdFromUri(uri: Uri): String? {
|
||||
val projection = arrayOf(ContactsContract.Data.CONTACT_ID)
|
||||
|
@ -202,101 +143,28 @@ class ContactsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
fun findContactById(id: String): Contact? {
|
||||
var found: Contact? = null
|
||||
if (contacts.isNotEmpty()) {
|
||||
found = contacts.find { contact ->
|
||||
contact is NativeContact && contact.nativeId == id
|
||||
}
|
||||
}
|
||||
|
||||
if (found == null) {
|
||||
Log.i("[Contacts Manager] Contact with id $id not found yet")
|
||||
} else {
|
||||
Log.d("[Contacts Manager] Found contact with id [$id]: ${found.fullName}")
|
||||
}
|
||||
|
||||
return found
|
||||
fun findContactById(id: String): Friend? {
|
||||
return coreContext.core.defaultFriendList?.findFriendByRefKey(id)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun findContactByPhoneNumber(number: String): Contact? {
|
||||
val contact = friendsMap[number]
|
||||
if (contact != null) return contact
|
||||
|
||||
val friend = coreContext.core.findFriendByPhoneNumber(number)
|
||||
val udContact = friend?.userData as? Contact
|
||||
if (udContact != null) friendsMap[number] = udContact
|
||||
return udContact
|
||||
fun findContactByPhoneNumber(number: String): Friend? {
|
||||
return coreContext.core.findFriendByPhoneNumber(number)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun findContactByFriend(friend: Friend): Contact? {
|
||||
val refKey = friend.refKey
|
||||
if (refKey != null) {
|
||||
val contact = findContactById(refKey)
|
||||
if (contact != null) return contact
|
||||
}
|
||||
|
||||
val address = friend.address
|
||||
var potentialContact: Contact? = null
|
||||
if (address != null) {
|
||||
val friends = coreContext.core.findFriends(address)
|
||||
for (f in friends) {
|
||||
if (f.name == friend.name) {
|
||||
val contact: Contact? = f.userData as? Contact
|
||||
if (contact != null) return contact
|
||||
} else {
|
||||
val contact: Contact? = f.userData as? Contact
|
||||
if (contact != null) potentialContact = contact
|
||||
}
|
||||
fun findContactByAddress(address: Address): Friend? {
|
||||
for (friend in localFriends) {
|
||||
val found = friend.addresses.find {
|
||||
it.weakEqual(address)
|
||||
}
|
||||
if (found != null) {
|
||||
return friend
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialContact != null) {
|
||||
return potentialContact
|
||||
}
|
||||
|
||||
for (list in coreContext.core.friendsLists) {
|
||||
for (f in list.friends) {
|
||||
if (f.name == friend.name) {
|
||||
val contact: Contact? = f.userData as? Contact
|
||||
if (contact != null) return contact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun findContactByAddress(address: Address, ignoreLocalContact: Boolean = false): Contact? {
|
||||
if (!ignoreLocalContact) {
|
||||
val localContact = localAccountsContacts.find { localContact ->
|
||||
localContact.sipAddresses.find { localAddress ->
|
||||
address.weakEqual(localAddress)
|
||||
} != null
|
||||
}
|
||||
if (localContact != null) return localContact
|
||||
}
|
||||
|
||||
val cleanAddress = address.clone()
|
||||
cleanAddress.clean() // To remove gruu if any
|
||||
val cleanStringAddress = cleanAddress.asStringUriOnly()
|
||||
|
||||
val cacheContact = friendsMap[cleanStringAddress]
|
||||
if (cacheContact != null) {
|
||||
return cacheContact
|
||||
}
|
||||
|
||||
val friends = coreContext.core.findFriends(address)
|
||||
for (friend in friends) {
|
||||
val contact: Contact? = friend?.userData as? Contact
|
||||
if (contact != null) {
|
||||
friendsMap[cleanStringAddress] = contact
|
||||
return contact
|
||||
}
|
||||
}
|
||||
val friend = coreContext.core.findFriend(address)
|
||||
if (friend != null) return friend
|
||||
|
||||
val username = address.username
|
||||
if (username != null && Patterns.PHONE.matcher(username).matches()) {
|
||||
|
@ -325,26 +193,15 @@ class ContactsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
fun notifyListeners(contact: Contact) {
|
||||
fun notifyListeners(friend: Friend) {
|
||||
val list = contactsUpdatedListeners.toMutableList()
|
||||
for (listener in list) {
|
||||
listener.onContactUpdated(contact)
|
||||
listener.onContactUpdated(friend)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun destroy() {
|
||||
context.contentResolver.unregisterContentObserver(contactsObserver)
|
||||
loadContactsTask?.cancel(true)
|
||||
|
||||
friendsMap.clear()
|
||||
// Contact has a Friend field and Friend can have a Contact has userData
|
||||
// Friend also keeps a ref on the Core, so we have to clean them
|
||||
for (contact in contacts) {
|
||||
contact.friend = null
|
||||
}
|
||||
contacts.clear()
|
||||
|
||||
val core = coreContext.core
|
||||
for (list in core.friendsLists) list.removeListener(friendListListener)
|
||||
}
|
||||
|
@ -403,20 +260,13 @@ class ContactsManager(private val context: Context) {
|
|||
|
||||
@Synchronized
|
||||
private fun refreshContactOnPresenceReceived(friend: Friend) {
|
||||
if (friend.userData == null) return
|
||||
|
||||
val contact: Contact = friend.userData as Contact
|
||||
Log.d("[Contacts Manager] Received presence information for contact $contact")
|
||||
Log.d("[Contacts Manager] Received presence information for contact $friend")
|
||||
if (corePreferences.storePresenceInNativeContact && PermissionHelper.get().hasWriteContactsPermission()) {
|
||||
if (contact is NativeContact) {
|
||||
storePresenceInNativeContact(contact)
|
||||
if (friend.refKey != null) {
|
||||
storePresenceInNativeContact(friend)
|
||||
}
|
||||
}
|
||||
if (loadContactsTask?.status == AsyncTask.Status.RUNNING) {
|
||||
Log.w("[Contacts Manager] Async contacts loader running, skip onContactUpdated listener notify")
|
||||
} else {
|
||||
notifyListeners(contact)
|
||||
}
|
||||
notifyListeners(friend)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
@ -424,33 +274,28 @@ class ContactsManager(private val context: Context) {
|
|||
if (corePreferences.storePresenceInNativeContact && PermissionHelper.get().hasWriteContactsPermission()) {
|
||||
for (list in coreContext.core.friendsLists) {
|
||||
for (friend in list.friends) {
|
||||
if (friend.userData == null) continue
|
||||
val contact: Contact = friend.userData as Contact
|
||||
if (contact is NativeContact) {
|
||||
storePresenceInNativeContact(contact)
|
||||
if (loadContactsTask?.status == AsyncTask.Status.RUNNING) {
|
||||
Log.w("[Contacts Manager] Async contacts loader running, skip onContactUpdated listener notify")
|
||||
} else {
|
||||
notifyListeners(contact)
|
||||
}
|
||||
val id = friend.refKey
|
||||
if (id != null) {
|
||||
storePresenceInNativeContact(friend)
|
||||
notifyListeners(friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun storePresenceInNativeContact(contact: NativeContact) {
|
||||
for (phoneNumber in contact.rawPhoneNumbers) {
|
||||
val sipAddress = contact.getContactForPhoneNumberOrAddress(phoneNumber)
|
||||
private fun storePresenceInNativeContact(friend: Friend) {
|
||||
for (phoneNumber in friend.phoneNumbersWithLabel) {
|
||||
val sipAddress = friend.getContactForPhoneNumberOrAddress(phoneNumber.phoneNumber)
|
||||
if (sipAddress != null) {
|
||||
Log.d("[Contacts Manager] Found presence information to store in native contact $contact under Linphone sync account")
|
||||
val contactEditor = NativeContactEditor(contact)
|
||||
Log.d("[Contacts Manager] Found presence information to store in native contact $friend under Linphone sync account")
|
||||
val contactEditor = NativeContactEditor(friend)
|
||||
val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
coroutineScope.launch {
|
||||
val deferred = async {
|
||||
withContext(Dispatchers.IO) {
|
||||
contactEditor.setPresenceInformation(
|
||||
phoneNumber,
|
||||
phoneNumber.phoneNumber,
|
||||
sipAddress
|
||||
).commit()
|
||||
}
|
||||
|
@ -460,4 +305,98 @@ class ContactsManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createFriendFromSearchResult(searchResult: SearchResult): Friend {
|
||||
val searchResultFriend = searchResult.friend
|
||||
if (searchResultFriend != null) return searchResultFriend
|
||||
|
||||
val friend = coreContext.core.createFriend()
|
||||
|
||||
val address = searchResult.address
|
||||
if (address != null) {
|
||||
friend.address = address
|
||||
}
|
||||
|
||||
val number = searchResult.phoneNumber
|
||||
if (number != null) {
|
||||
friend.addPhoneNumber(number)
|
||||
|
||||
if (address != null && address.username == number) {
|
||||
friend.removeAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
return friend
|
||||
}
|
||||
}
|
||||
|
||||
fun Friend.getContactForPhoneNumberOrAddress(value: String): String? {
|
||||
val presenceModel = getPresenceModelForUriOrTel(value)
|
||||
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact
|
||||
return null
|
||||
}
|
||||
|
||||
fun Friend.hasPresence(): Boolean {
|
||||
for (address in addresses) {
|
||||
val presenceModel = getPresenceModelForUriOrTel(address.asStringUriOnly())
|
||||
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return true
|
||||
}
|
||||
for (number in phoneNumbersWithLabel) {
|
||||
val presenceModel = getPresenceModelForUriOrTel(number.phoneNumber)
|
||||
if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun Friend.getPictureUri(): Uri? {
|
||||
val refKey = refKey
|
||||
if (refKey != null) {
|
||||
try {
|
||||
val nativeId = refKey.toLong()
|
||||
return Uri.withAppendedPath(
|
||||
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, nativeId),
|
||||
ContactsContract.Contacts.Photo.DISPLAY_PHOTO
|
||||
)
|
||||
} catch (nfe: NumberFormatException) {}
|
||||
}
|
||||
|
||||
val photoUri = photo ?: return null
|
||||
return Uri.parse(photoUri)
|
||||
}
|
||||
|
||||
fun Friend.getThumbnailUri(): Uri? {
|
||||
val refKey = refKey
|
||||
if (refKey != null) {
|
||||
try {
|
||||
val nativeId = refKey.toLong()
|
||||
return Uri.withAppendedPath(
|
||||
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, nativeId),
|
||||
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
|
||||
)
|
||||
} catch (nfe: NumberFormatException) {}
|
||||
}
|
||||
|
||||
val photoUri = photo ?: return null
|
||||
return Uri.parse(photoUri)
|
||||
}
|
||||
|
||||
fun Friend.getPerson(): Person {
|
||||
val personBuilder = Person.Builder().setName(name)
|
||||
|
||||
val bm: Bitmap? =
|
||||
ImageUtils.getRoundBitmapFromUri(
|
||||
coreContext.context,
|
||||
getPictureUri()
|
||||
)
|
||||
val icon =
|
||||
if (bm == null) IconCompat.createWithResource(
|
||||
coreContext.context,
|
||||
R.drawable.voip_single_contact_avatar_alt
|
||||
) else IconCompat.createWithAdaptiveBitmap(bm)
|
||||
if (icon != null) {
|
||||
personBuilder.setIcon(icon)
|
||||
}
|
||||
|
||||
personBuilder.setImportant(starred)
|
||||
return personBuilder.build()
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ open class ContactsSelectionViewModel : MessageNotifierViewModel() {
|
|||
val contact = coreContext.contactsManager.findContactByAddress(address)
|
||||
if (contact != null) {
|
||||
val clone = address.clone()
|
||||
clone.displayName = contact.fullName
|
||||
clone.displayName = contact.name
|
||||
list.add(clone)
|
||||
} else {
|
||||
list.add(address)
|
||||
|
|
|
@ -1,274 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-android
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.linphone.contact
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.util.Patterns
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.LinphoneApplication.Companion.corePreferences
|
||||
import org.linphone.R
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.SubscribePolicy
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.ImageUtils
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class NativeContact(val nativeId: String, private val lookupKey: String? = null) : Contact() {
|
||||
override fun compareTo(other: Contact): Int {
|
||||
val superResult = super.compareTo(other)
|
||||
if (superResult == 0 && other is NativeContact) {
|
||||
return nativeId.compareTo(other.nativeId)
|
||||
}
|
||||
return superResult
|
||||
}
|
||||
|
||||
override fun getContactThumbnailPictureUri(): Uri {
|
||||
return Uri.withAppendedPath(
|
||||
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, nativeId.toLong()),
|
||||
ContactsContract.Contacts.Photo.CONTENT_DIRECTORY
|
||||
)
|
||||
}
|
||||
|
||||
override fun getContactPictureUri(): Uri {
|
||||
return Uri.withAppendedPath(
|
||||
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, nativeId.toLong()),
|
||||
ContactsContract.Contacts.Photo.DISPLAY_PHOTO
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPerson(): Person {
|
||||
val personBuilder = Person.Builder().setName(fullName)
|
||||
|
||||
val bm: Bitmap? =
|
||||
ImageUtils.getRoundBitmapFromUri(
|
||||
coreContext.context,
|
||||
getContactThumbnailPictureUri()
|
||||
)
|
||||
val icon =
|
||||
if (bm == null) IconCompat.createWithResource(
|
||||
coreContext.context,
|
||||
R.drawable.voip_single_contact_avatar_alt
|
||||
) else IconCompat.createWithAdaptiveBitmap(bm)
|
||||
if (icon != null) {
|
||||
personBuilder.setIcon(icon)
|
||||
}
|
||||
|
||||
personBuilder.setImportant(isStarred)
|
||||
if (lookupKey != null) {
|
||||
personBuilder.setUri("${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey")
|
||||
}
|
||||
|
||||
return personBuilder.build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun syncValuesFromAndroidCursor(cursor: Cursor) {
|
||||
try {
|
||||
val displayName: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.DISPLAY_NAME_PRIMARY))
|
||||
|
||||
val mime: String? =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE))
|
||||
val data1: String? = cursor.getString(cursor.getColumnIndexOrThrow("data1"))
|
||||
val data2: String? = cursor.getString(cursor.getColumnIndexOrThrow("data2"))
|
||||
val data3: String? = cursor.getString(cursor.getColumnIndexOrThrow("data3"))
|
||||
val data4: String? = cursor.getString(cursor.getColumnIndexOrThrow("data4"))
|
||||
|
||||
if (fullName == null || fullName != displayName) {
|
||||
Log.d("[Native Contact] Setting display name $displayName")
|
||||
fullName = displayName
|
||||
}
|
||||
|
||||
val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type)
|
||||
when (mime) {
|
||||
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
|
||||
if (data1 == null && data4 == null) {
|
||||
Log.d("[Native Contact] Phone number data is empty")
|
||||
return
|
||||
}
|
||||
|
||||
val labelColumnIndex =
|
||||
cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)
|
||||
val label: String? = cursor.getString(labelColumnIndex)
|
||||
val typeColumnIndex =
|
||||
cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)
|
||||
val type: Int = cursor.getInt(typeColumnIndex)
|
||||
val typeLabel = ContactsContract.CommonDataKinds.Phone.getTypeLabel(
|
||||
coreContext.context.resources,
|
||||
type,
|
||||
label
|
||||
).toString()
|
||||
|
||||
// data4 = ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER
|
||||
// data1 = ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||
val number = if (corePreferences.preferNormalizedPhoneNumbersFromAddressBook ||
|
||||
data1.isNullOrEmpty() ||
|
||||
!Patterns.PHONE.matcher(data1).matches()
|
||||
) {
|
||||
data4 ?: data1
|
||||
} else {
|
||||
data1
|
||||
}
|
||||
|
||||
if (number != null && number.isNotEmpty()) {
|
||||
Log.d("[Native Contact] Found phone number $data1 ($data4), type label is $typeLabel")
|
||||
val trimmedNumber = LinphoneUtils.trimPhoneNumber(number)
|
||||
val found = rawPhoneNumbers.find { LinphoneUtils.trimPhoneNumber(it) == trimmedNumber }
|
||||
if (found == null) {
|
||||
phoneNumbers.add(PhoneNumber(number, typeLabel))
|
||||
rawPhoneNumbers.add(number)
|
||||
}
|
||||
}
|
||||
}
|
||||
linphoneMime, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> {
|
||||
if (data1 == null) {
|
||||
Log.d("[Native Contact] SIP address is null")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("[Native Contact] Found SIP address $data1")
|
||||
if (rawPhoneNumbers.contains(data1)) {
|
||||
Log.d("[Native Contact] SIP address value already exists in phone numbers list, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
val address: Address? = coreContext.core.interpretUrl(data1)
|
||||
if (address == null) {
|
||||
Log.e("[Native Contact] Couldn't parse address $data1 !")
|
||||
return
|
||||
}
|
||||
|
||||
val stringAddress = address.asStringUriOnly()
|
||||
Log.d("[Native Contact] Found SIP address $stringAddress")
|
||||
if (!rawSipAddresses.contains(data1)) {
|
||||
sipAddresses.add(address)
|
||||
rawSipAddresses.add(data1)
|
||||
}
|
||||
}
|
||||
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> {
|
||||
if (data1 == null) {
|
||||
Log.d("[Native Contact] Organization is null")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("[Native Contact] Found organization $data1")
|
||||
organization = data1
|
||||
}
|
||||
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
|
||||
if (data2 == null && data3 == null) {
|
||||
Log.d("[Native Contact] First name and last name are both null")
|
||||
return
|
||||
}
|
||||
|
||||
Log.d("[Native Contact] Found first name $data2 and last name $data3")
|
||||
firstName = data2
|
||||
lastName = data3
|
||||
}
|
||||
}
|
||||
} catch (iae: IllegalArgumentException) {
|
||||
Log.e("[Native Contact] Exception: $iae")
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun createOrUpdateFriendFromNativeContact() {
|
||||
var created = false
|
||||
if (friend == null) {
|
||||
val friend = coreContext.core.createFriend()
|
||||
friend.isSubscribesEnabled = false
|
||||
friend.incSubscribePolicy = SubscribePolicy.SPDeny
|
||||
friend.refKey = nativeId
|
||||
friend.userData = this
|
||||
|
||||
created = true
|
||||
this.friend = friend
|
||||
}
|
||||
|
||||
val friend = this.friend
|
||||
if (friend != null) {
|
||||
friend.edit()
|
||||
val fn = fullName
|
||||
if (fn != null) friend.name = fn
|
||||
|
||||
val vCard = friend.vcard
|
||||
if (vCard != null) {
|
||||
vCard.familyName = lastName
|
||||
vCard.givenName = firstName
|
||||
vCard.organization = organization
|
||||
}
|
||||
|
||||
if (!created) {
|
||||
for (address in friend.addresses) friend.removeAddress(address)
|
||||
for (number in friend.phoneNumbers) friend.removePhoneNumber(number)
|
||||
}
|
||||
|
||||
for (address in sipAddresses) friend.addAddress(address)
|
||||
for (number in rawPhoneNumbers) friend.addPhoneNumber(number)
|
||||
|
||||
friend.done()
|
||||
if (created) {
|
||||
coreContext.core.defaultFriendList?.addFriend(friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun syncValuesFromAndroidContact(context: Context) {
|
||||
Log.d("[Native Contact] Looking for contact cursor with id: $nativeId")
|
||||
|
||||
var selection: String = ContactsContract.Data.CONTACT_ID + " == " + nativeId
|
||||
if (corePreferences.fetchContactsFromDefaultDirectory) {
|
||||
Log.d("[Native Contact] Only fetching contacts in default directory")
|
||||
selection = ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1 AND " + selection
|
||||
}
|
||||
|
||||
val cursor: Cursor? = context.contentResolver
|
||||
.query(
|
||||
ContactsContract.Data.CONTENT_URI,
|
||||
AsyncContactsLoader.projection,
|
||||
selection,
|
||||
null,
|
||||
null
|
||||
)
|
||||
if (cursor != null) {
|
||||
sipAddresses.clear()
|
||||
rawSipAddresses.clear()
|
||||
phoneNumbers.clear()
|
||||
rawPhoneNumbers.clear()
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
syncValuesFromAndroidCursor(cursor)
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${super.toString()}: id [$nativeId], name [$fullName]"
|
||||
}
|
||||
}
|
|
@ -28,11 +28,12 @@ import android.provider.ContactsContract.RawContacts
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.activities.main.contact.data.NumberOrAddressEditorData
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.PermissionHelper
|
||||
|
||||
class NativeContactEditor(val contact: NativeContact) {
|
||||
class NativeContactEditor(val friend: Friend) {
|
||||
companion object {
|
||||
fun createAndroidContact(accountName: String?, accountType: String?): Long {
|
||||
Log.i("[Native Contact Editor] Using sync account $accountName with type $accountType")
|
||||
|
@ -94,7 +95,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
RawContacts.CONTENT_URI,
|
||||
arrayOf(RawContacts._ID),
|
||||
"${RawContacts.CONTACT_ID} =?",
|
||||
arrayOf(contact.nativeId),
|
||||
arrayOf(friend.refKey),
|
||||
null
|
||||
)
|
||||
if (cursor?.moveToFirst() == true) {
|
||||
|
@ -102,7 +103,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
if (rawId == null) {
|
||||
try {
|
||||
rawId = cursor.getString(cursor.getColumnIndexOrThrow(RawContacts._ID))
|
||||
Log.i("[Native Contact Editor] Found raw id $rawId for native contact with id ${contact.nativeId}")
|
||||
Log.i("[Native Contact Editor] Found raw id $rawId for native contact with id ${friend.refKey}")
|
||||
} catch (iae: IllegalArgumentException) {
|
||||
Log.e("[Native Contact Editor] Exception: $iae")
|
||||
}
|
||||
|
@ -113,12 +114,12 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
}
|
||||
|
||||
fun setFirstAndLastNames(firstName: String, lastName: String): NativeContactEditor {
|
||||
if (firstName == contact.firstName && lastName == contact.lastName) {
|
||||
if (firstName == friend.vcard?.givenName && lastName == friend.vcard?.familyName) {
|
||||
Log.w("[Native Contact Editor] First & last names haven't changed")
|
||||
return this
|
||||
}
|
||||
|
||||
val builder = if (contact.firstName == null && contact.lastName == null) {
|
||||
val builder = if (friend.vcard?.givenName == null && friend.vcard?.familyName == null) {
|
||||
// Probably a contact creation
|
||||
ContentProviderOperation.newInsert(contactUri)
|
||||
.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawId)
|
||||
|
@ -126,7 +127,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
ContentProviderOperation.newUpdate(contactUri)
|
||||
.withSelection(
|
||||
selection,
|
||||
arrayOf(contact.nativeId, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
arrayOf(friend.refKey, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -145,18 +146,18 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
}
|
||||
|
||||
fun setOrganization(value: String): NativeContactEditor {
|
||||
val previousValue = contact.organization
|
||||
val previousValue = friend.vcard?.organization.orEmpty()
|
||||
if (value == previousValue) {
|
||||
Log.d("[Native Contact Editor] Organization hasn't changed")
|
||||
return this
|
||||
}
|
||||
|
||||
val builder = if (previousValue?.isNotEmpty() == true) {
|
||||
val builder = if (previousValue.isNotEmpty()) {
|
||||
ContentProviderOperation.newUpdate(contactUri)
|
||||
.withSelection(
|
||||
"$selection AND ${CommonDataKinds.Organization.COMPANY} =?",
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
CommonDataKinds.Organization.CONTENT_ITEM_TYPE,
|
||||
previousValue
|
||||
)
|
||||
|
@ -261,7 +262,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
RawContacts.CONTENT_URI,
|
||||
arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE),
|
||||
"${RawContacts.CONTACT_ID} =?",
|
||||
arrayOf(contact.nativeId),
|
||||
arrayOf(friend.refKey),
|
||||
null
|
||||
)
|
||||
if (cursor?.moveToFirst() == true) {
|
||||
|
@ -272,7 +273,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
if (accountType == AppUtils.getString(R.string.sync_account_type) && syncAccountRawId == null) {
|
||||
syncAccountRawId =
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(RawContacts._ID))
|
||||
Log.d("[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${contact.nativeId}")
|
||||
Log.d("[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${friend.refKey}")
|
||||
}
|
||||
} catch (iae: IllegalArgumentException) {
|
||||
Log.e("[Native Contact Editor] Exception: $iae")
|
||||
|
@ -369,7 +370,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
.withSelection(
|
||||
phoneNumberSelection,
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
|
||||
currentValue,
|
||||
currentValue
|
||||
|
@ -390,7 +391,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
.withSelection(
|
||||
phoneNumberSelection,
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
|
||||
phoneNumber,
|
||||
phoneNumber
|
||||
|
@ -417,7 +418,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
.withSelection(
|
||||
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data1=?",
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
AppUtils.getString(R.string.linphone_address_mime_type),
|
||||
currentValue
|
||||
)
|
||||
|
@ -431,7 +432,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
.withSelection(
|
||||
"${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data1=?",
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
|
||||
currentValue
|
||||
)
|
||||
|
@ -448,7 +449,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
.withSelection(
|
||||
"${ContactsContract.Data.CONTACT_ID} =? AND (${ContactsContract.Data.MIMETYPE} =? OR ${ContactsContract.Data.MIMETYPE} =?) AND data1=?",
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
|
||||
AppUtils.getString(R.string.linphone_address_mime_type),
|
||||
sipAddress
|
||||
|
@ -518,7 +519,7 @@ class NativeContactEditor(val contact: NativeContact) {
|
|||
.withSelection(
|
||||
presenceUpdateSelection,
|
||||
arrayOf(
|
||||
contact.nativeId,
|
||||
friend.refKey,
|
||||
AppUtils.getString(R.string.linphone_address_mime_type),
|
||||
phoneNumber
|
||||
)
|
||||
|
|
|
@ -33,7 +33,8 @@ import android.util.Pair
|
|||
import android.view.*
|
||||
import androidx.emoji.bundled.BundledEmojiCompatConfig
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.*
|
||||
import androidx.loader.app.LoaderManager
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import java.io.File
|
||||
import java.math.BigInteger
|
||||
|
@ -53,8 +54,9 @@ import org.linphone.LinphoneApplication.Companion.corePreferences
|
|||
import org.linphone.R
|
||||
import org.linphone.compatibility.Compatibility
|
||||
import org.linphone.compatibility.PhoneStateInterface
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.ContactLoader
|
||||
import org.linphone.contact.ContactsManager
|
||||
import org.linphone.contact.getContactForPhoneNumberOrAddress
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.mediastream.Version
|
||||
import org.linphone.notifications.NotificationsManager
|
||||
|
@ -62,7 +64,21 @@ import org.linphone.telecom.TelecomHelper
|
|||
import org.linphone.utils.*
|
||||
import org.linphone.utils.Event
|
||||
|
||||
class CoreContext(val context: Context, coreConfig: Config) {
|
||||
class CoreContext(val context: Context, coreConfig: Config) : LifecycleOwner, ViewModelStoreOwner {
|
||||
private val _lifecycleRegistry = LifecycleRegistry(this)
|
||||
override fun getLifecycle(): Lifecycle {
|
||||
return _lifecycleRegistry
|
||||
}
|
||||
|
||||
private val _viewModelStore = ViewModelStore()
|
||||
override fun getViewModelStore(): ViewModelStore {
|
||||
return _viewModelStore
|
||||
}
|
||||
|
||||
private val contactLoader = ContactLoader()
|
||||
|
||||
private val collator: Collator = Collator.getInstance()
|
||||
|
||||
var stopped = false
|
||||
val core: Core
|
||||
val handler: Handler = Handler(Looper.getMainLooper())
|
||||
|
@ -84,10 +100,10 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
"$sdkVersion ($sdkBranch, $sdkBuildType)"
|
||||
}
|
||||
|
||||
val collator: Collator = Collator.getInstance()
|
||||
val contactsManager: ContactsManager by lazy {
|
||||
ContactsManager(context)
|
||||
}
|
||||
|
||||
val notificationsManager: NotificationsManager by lazy {
|
||||
NotificationsManager(context)
|
||||
}
|
||||
|
@ -109,7 +125,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
|
||||
Log.i("[Context] Global state changed [$state]")
|
||||
if (state == GlobalState.On) {
|
||||
contactsManager.fetchContactsAsync()
|
||||
fetchContacts()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,8 +166,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
answerCall(call)
|
||||
} else {
|
||||
Log.i("[Context] Scheduling auto answering in $autoAnswerDelay milliseconds")
|
||||
val mainThreadHandler = Handler(Looper.getMainLooper())
|
||||
mainThreadHandler.postDelayed(
|
||||
handler.postDelayed(
|
||||
{
|
||||
Log.w("[Context] Auto answering call")
|
||||
answerCall(call)
|
||||
|
@ -277,6 +292,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
|
||||
core = Factory.instance().createCoreWithConfig(coreConfig, context)
|
||||
stopped = false
|
||||
_lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
|
||||
Log.i("[Context] Ready")
|
||||
}
|
||||
|
||||
|
@ -300,7 +316,9 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
|
||||
configureCore()
|
||||
|
||||
_lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||
core.start()
|
||||
_lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
|
||||
initPhoneStateListener()
|
||||
|
||||
|
@ -318,6 +336,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
notificationsManager.startForeground()
|
||||
}
|
||||
|
||||
_lifecycleRegistry.currentState = Lifecycle.State.RESUMED
|
||||
Log.i("[Context] Started")
|
||||
}
|
||||
|
||||
|
@ -339,6 +358,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
core.stop()
|
||||
core.removeListener(listener)
|
||||
stopped = true
|
||||
_lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
// TODO: FIXME: uncomment
|
||||
// loggingService.removeListener(loggingServiceListener)
|
||||
}
|
||||
|
@ -440,6 +460,13 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
core.userCertificatesPath = userCertsPath
|
||||
}
|
||||
|
||||
fun fetchContacts() {
|
||||
if (PermissionHelper.required(context).hasReadContactsPermission()) {
|
||||
Log.i("[Context] Init contacts loader")
|
||||
LoaderManager.getInstance(this@CoreContext).initLoader(0, null, contactLoader)
|
||||
}
|
||||
}
|
||||
|
||||
/* Call related functions */
|
||||
|
||||
fun initPhoneStateListener() {
|
||||
|
@ -552,7 +579,7 @@ class CoreContext(val context: Context, coreConfig: Config) {
|
|||
fun startCall(to: String) {
|
||||
var stringAddress = to
|
||||
if (android.util.Patterns.PHONE.matcher(to).matches()) {
|
||||
val contact: Contact? = contactsManager.findContactByPhoneNumber(to)
|
||||
val contact = contactsManager.findContactByPhoneNumber(to)
|
||||
val alias = contact?.getContactForPhoneNumberOrAddress(to)
|
||||
if (alias != null) {
|
||||
Log.i("[Context] Found matching alias $alias for phone number $to, using it")
|
||||
|
|
|
@ -45,7 +45,8 @@ import org.linphone.activities.chat_bubble.ChatBubbleActivity
|
|||
import org.linphone.activities.main.MainActivity
|
||||
import org.linphone.activities.voip.CallActivity
|
||||
import org.linphone.compatibility.Compatibility
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.getPerson
|
||||
import org.linphone.contact.getThumbnailUri
|
||||
import org.linphone.core.*
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
|
@ -65,7 +66,7 @@ class Notifiable(val notificationId: Int) {
|
|||
|
||||
class NotifiableMessage(
|
||||
var message: String,
|
||||
val contact: Contact?,
|
||||
val friend: Friend?,
|
||||
val sender: String,
|
||||
val time: Long,
|
||||
val senderAvatar: Bitmap? = null,
|
||||
|
@ -421,9 +422,9 @@ class NotificationsManager(private val context: Context) {
|
|||
return notifiable
|
||||
}
|
||||
|
||||
fun getPerson(contact: Contact?, displayName: String, picture: Bitmap?): Person {
|
||||
return if (contact != null) {
|
||||
contact.getPerson()
|
||||
fun getPerson(friend: Friend?, displayName: String, picture: Bitmap?): Person {
|
||||
return if (friend != null) {
|
||||
friend.getPerson()
|
||||
} else {
|
||||
val builder = Person.Builder().setName(displayName)
|
||||
val userIcon =
|
||||
|
@ -488,9 +489,9 @@ class NotificationsManager(private val context: Context) {
|
|||
.format(missedCallCount)
|
||||
Log.i("[Notifications Manager] Updating missed calls notification count to $missedCallCount")
|
||||
} else {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(remoteAddress)
|
||||
val friend: Friend? = coreContext.contactsManager.findContactByAddress(remoteAddress)
|
||||
body = context.getString(R.string.missed_call_notification_body)
|
||||
.format(contact?.fullName ?: LinphoneUtils.getDisplayName(remoteAddress))
|
||||
.format(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress))
|
||||
Log.i("[Notifications Manager] Creating missed call notification")
|
||||
}
|
||||
|
||||
|
@ -613,15 +614,15 @@ class NotificationsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
private fun displayIncomingChatNotification(room: ChatRoom, message: ChatMessage) {
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(message.fromAddress)
|
||||
val friend = coreContext.contactsManager.findContactByAddress(message.fromAddress)
|
||||
|
||||
val notifiable = getNotifiableForRoom(room)
|
||||
if (notifiable.messages.isNotEmpty() || room.unreadMessagesCount == 1) {
|
||||
val notifiableMessage = getNotifiableMessage(message, contact)
|
||||
val notifiableMessage = getNotifiableMessage(message, friend)
|
||||
notifiable.messages.add(notifiableMessage)
|
||||
} else {
|
||||
for (chatMessage in room.unreadHistory) {
|
||||
val notifiableMessage = getNotifiableMessage(chatMessage, contact)
|
||||
val notifiableMessage = getNotifiableMessage(chatMessage, friend)
|
||||
notifiable.messages.add(notifiableMessage)
|
||||
}
|
||||
}
|
||||
|
@ -650,10 +651,9 @@ class NotificationsManager(private val context: Context) {
|
|||
return notifiable
|
||||
}
|
||||
|
||||
private fun getNotifiableMessage(message: ChatMessage, contact: Contact?): NotifiableMessage {
|
||||
val pictureUri = contact?.getContactThumbnailPictureUri()
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, pictureUri)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(message.fromAddress)
|
||||
private fun getNotifiableMessage(message: ChatMessage, friend: Friend?): NotifiableMessage {
|
||||
val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri())
|
||||
val displayName = friend?.name ?: LinphoneUtils.getDisplayName(message.fromAddress)
|
||||
var text = ""
|
||||
|
||||
val isConferenceInvite = message.contents.firstOrNull()?.isIcalendar ?: false
|
||||
|
@ -671,7 +671,7 @@ class NotificationsManager(private val context: Context) {
|
|||
|
||||
val notifiableMessage = NotifiableMessage(
|
||||
text,
|
||||
contact,
|
||||
friend,
|
||||
displayName,
|
||||
message.time,
|
||||
senderAvatar = roundPicture,
|
||||
|
@ -765,8 +765,8 @@ class NotificationsManager(private val context: Context) {
|
|||
|
||||
var lastPerson: Person? = null
|
||||
for (message in notifiable.messages) {
|
||||
val contact = message.contact
|
||||
val person = getPerson(contact, message.sender, message.senderAvatar)
|
||||
val friend = message.friend
|
||||
val person = getPerson(friend, message.sender, message.senderAvatar)
|
||||
|
||||
// We don't want to see our own avatar
|
||||
if (!message.isOutgoing) {
|
||||
|
|
|
@ -33,7 +33,6 @@ import android.telecom.TelecomManager.*
|
|||
import java.lang.Exception
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.Core
|
||||
import org.linphone.core.CoreListenerStub
|
||||
|
@ -228,8 +227,8 @@ class TelecomHelper private constructor(context: Context) {
|
|||
|
||||
extras.putString("Call-ID", call.callLog.callId)
|
||||
|
||||
val contact: Contact? = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val displayName = contact?.fullName ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
val contact = coreContext.contactsManager.findContactByAddress(call.remoteAddress)
|
||||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress)
|
||||
extras.putString("DisplayName", displayName)
|
||||
|
||||
return extras
|
||||
|
|
|
@ -42,13 +42,13 @@ class ContactUtils {
|
|||
return null
|
||||
}
|
||||
|
||||
val vcard = contact.friend?.vcard?.asVcard4String()
|
||||
val vcard = contact.vcard?.asVcard4String()
|
||||
if (vcard == null) {
|
||||
Log.e("[Contact Utils] Failed to get vCard from contact $contactID")
|
||||
return null
|
||||
}
|
||||
|
||||
val contactName = contact.fullName?.replace(" ", "_") ?: contactID
|
||||
val contactName = contact.name?.replace(" ", "_") ?: contactID
|
||||
val vcardPath = FileUtils.getFileStoragePath("$contactName.vcf")
|
||||
val inputStream = ByteArrayInputStream(vcard.toByteArray())
|
||||
try {
|
||||
|
|
|
@ -176,7 +176,11 @@ class LinphoneUtils {
|
|||
return "${localSipUri.asStringUriOnly()}~${remoteSipUri.asStringUriOnly()}"
|
||||
}
|
||||
|
||||
fun trimPhoneNumber(phoneNumber: String): String {
|
||||
fun arePhoneNumberWeakEqual(number1: String, number2: String): Boolean {
|
||||
return trimPhoneNumber(number1) == trimPhoneNumber(number2)
|
||||
}
|
||||
|
||||
private fun trimPhoneNumber(phoneNumber: String): String {
|
||||
return phoneNumber.replace(" ", "")
|
||||
.replace("-", "")
|
||||
.replace("(", "")
|
||||
|
|
|
@ -33,11 +33,11 @@ import androidx.core.graphics.drawable.IconCompat
|
|||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.activities.main.MainActivity
|
||||
import org.linphone.contact.Contact
|
||||
import org.linphone.contact.NativeContact
|
||||
import org.linphone.contact.getPerson
|
||||
import org.linphone.core.Address
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.ChatRoomCapabilities
|
||||
import org.linphone.core.Friend
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.mediastream.Version
|
||||
|
||||
|
@ -78,10 +78,10 @@ class ShortcutsHelper(val context: Context) {
|
|||
val stringAddress = address.asStringUriOnly()
|
||||
if (!processedAddresses.contains(stringAddress)) {
|
||||
processedAddresses.add(stringAddress)
|
||||
val contact: Contact? =
|
||||
val contact: Friend? =
|
||||
coreContext.contactsManager.findContactByAddress(address)
|
||||
|
||||
if (contact != null && contact is NativeContact) {
|
||||
if (contact != null && contact.refKey != null) {
|
||||
val shortcut: ShortcutInfo? = createContactShortcut(context, contact)
|
||||
if (shortcut != null) {
|
||||
Log.i("[Shortcut Helper] Creating launcher shortcut for ${shortcut.shortLabel}")
|
||||
|
@ -97,27 +97,28 @@ class ShortcutsHelper(val context: Context) {
|
|||
shortcutManager.dynamicShortcuts = shortcuts
|
||||
}
|
||||
|
||||
private fun createContactShortcut(context: Context, contact: NativeContact): ShortcutInfo? {
|
||||
private fun createContactShortcut(context: Context, contact: Friend): ShortcutInfo? {
|
||||
try {
|
||||
val categories: ArraySet<String> = ArraySet()
|
||||
categories.add(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION)
|
||||
|
||||
val person = contact.getPerson()
|
||||
val icon = person.icon
|
||||
val id = contact.refKey ?: return null
|
||||
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.setClass(context, MainActivity::class.java)
|
||||
intent.putExtra("ContactId", contact.nativeId)
|
||||
intent.putExtra("ContactId", id)
|
||||
|
||||
return ShortcutInfoCompat.Builder(context, contact.nativeId)
|
||||
.setShortLabel(contact.fullName ?: "${contact.firstName} ${contact.lastName}")
|
||||
return ShortcutInfoCompat.Builder(context, id)
|
||||
.setShortLabel(contact.name ?: "")
|
||||
.setIcon(icon)
|
||||
.setPerson(person)
|
||||
.setCategories(categories)
|
||||
.setIntent(intent)
|
||||
.build().toShortcutInfo()
|
||||
} catch (e: Exception) {
|
||||
Log.e("[Shortcuts Helper] createContactShortcut for contact [${contact.fullName}] exception: $e")
|
||||
Log.e("[Shortcuts Helper] createContactShortcut for contact [${contact.name}] exception: $e")
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -168,7 +169,7 @@ class ShortcutsHelper(val context: Context) {
|
|||
if (contact != null) {
|
||||
personsList.add(contact.getPerson())
|
||||
}
|
||||
subject = contact?.fullName ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress)
|
||||
subject = contact?.name ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress)
|
||||
icon = contact?.getPerson()?.icon ?: IconCompat.createWithResource(context, R.drawable.voip_single_contact_avatar_alt)
|
||||
} else if (chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) && chatRoom.participants.isNotEmpty()) {
|
||||
val address = chatRoom.participants.first().address
|
||||
|
@ -177,7 +178,7 @@ class ShortcutsHelper(val context: Context) {
|
|||
if (contact != null) {
|
||||
personsList.add(contact.getPerson())
|
||||
}
|
||||
subject = contact?.fullName ?: LinphoneUtils.getDisplayName(address)
|
||||
subject = contact?.name ?: LinphoneUtils.getDisplayName(address)
|
||||
icon = contact?.getPerson()?.icon ?: IconCompat.createWithResource(context, R.drawable.voip_single_contact_avatar_alt)
|
||||
} else {
|
||||
for (participant in chatRoom.participants) {
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
android:paddingLeft="5dp">
|
||||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.fullName ?? viewModel.displayName) : viewModel.subject}"
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.name ?? viewModel.displayName) : viewModel.subject}"
|
||||
style="@style/toolbar_small_title_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -235,7 +235,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:text="@{data.chatMessage.outgoing ? data.time : data.time + ` - ` + (data.contact.fullName ?? data.displayName)}"
|
||||
android:text="@{data.chatMessage.outgoing ? data.time : data.time + ` - ` + (data.contact.name ?? data.displayName)}"
|
||||
android:visibility="@{data.hideTime ? View.GONE : View.VISIBLE}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/chat_message_reply_sender_font"
|
||||
android:text="@{data.contact.fullName ?? data.displayName, default=Tintin}"/>
|
||||
android:text="@{data.contact.name ?? data.displayName, default=Tintin}"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:visibility="@{data.contents.size() == 0 ? View.GONE : View.VISIBLE}"
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/chat_message_reply_sender_font"
|
||||
android:text="@{data.contact.fullName ?? data.displayName, default=Tintin}"/>
|
||||
android:text="@{data.contact.name ?? data.displayName, default=Tintin}"/>
|
||||
|
||||
<TextView
|
||||
android:visibility="@{data.text.length() == 0 ? View.GONE : View.VISIBLE}"
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:onClick="@{titleClickListener}"
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.fullName ?? viewModel.displayName) : viewModel.subject}"
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.name ?? viewModel.displayName) : viewModel.subject}"
|
||||
style="@style/toolbar_small_title_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:text="@{data.contact.fullName ?? data.displayName}"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:text="@{data.contact.fullName ?? data.displayName}"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:text="@{data.contact.fullName ?? data.displayName}"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.fullName ?? viewModel.displayName) : viewModel.subject}"
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.name ?? viewModel.displayName) : viewModel.subject}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="30dp"
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="top|left"
|
||||
android:lines="1"
|
||||
android:text="@{data.contact.fullName ?? data.displayName}" />
|
||||
android:text="@{data.contact.name ?? data.displayName}" />
|
||||
|
||||
<TextView
|
||||
style="@style/conference_scheduling_participant_sip_uri_font"
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
<variable
|
||||
name="initials"
|
||||
type="String" />
|
||||
<variable
|
||||
name="thumbnailPath"
|
||||
type="android.net.Uri" />
|
||||
<variable
|
||||
name="imagePath"
|
||||
type="android.net.Uri" />
|
||||
|
@ -52,6 +55,15 @@
|
|||
android:singleLine="true"
|
||||
android:ellipsize="none" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@null"
|
||||
android:background="?attr/backgroundColor"
|
||||
app:glideAvatar="@{thumbnailPath}" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
app:viewModel="@{viewModel}"/>
|
||||
|
||||
<TextView
|
||||
android:text="@{viewModel.contact.fullName}"
|
||||
android:text="@{viewModel.contact.name ?? viewModel.displayName}"
|
||||
style="@style/big_contact_name_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
android:src="@drawable/linphone_logo_tinted"
|
||||
android:layout_marginRight="10dp"
|
||||
android:contentDescription="@string/content_description_linphone_user"
|
||||
android:visibility="@{viewModel.contact.hasPresence() ? View.VISIBLE : View.GONE}" />
|
||||
android:visibility="@{viewModel.hasPresence() ? View.VISIBLE : View.GONE}" />
|
||||
|
||||
<CheckBox
|
||||
android:onClick="@{() -> selectionListViewModel.onToggleSelect(position)}"
|
||||
|
@ -80,7 +80,7 @@
|
|||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:id="@+id/name"
|
||||
android:text="@{viewModel.contact.fullName}"
|
||||
android:text="@{viewModel.contact.name ?? viewModel.displayName}"
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:text="@{data.contact.fullName ?? data.displayName}"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
style="@style/contact_name_list_cell_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
<ImageView
|
||||
android:onClick="@{contactClickListener}"
|
||||
android:visibility="@{viewModel.contact == null ? View.GONE : View.VISIBLE}"
|
||||
android:visibility="@{viewModel.contact == null || viewModel.contact.refKey == null ? View.GONE : View.VISIBLE}"
|
||||
android:contentDescription="@string/content_description_go_to_contact"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -97,7 +97,7 @@
|
|||
app:viewModel="@{viewModel}"/>
|
||||
|
||||
<TextView
|
||||
android:text="@{viewModel.contact.fullName ?? viewModel.displayName}"
|
||||
android:text="@{viewModel.contact.name ?? viewModel.displayName}"
|
||||
style="@style/big_contact_name_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
android:layout_toRightOf="@id/icon"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:text="@{(viewModel.isConferenceCallLog ? viewModel.conferenceSubject : viewModel.contact.fullName ?? viewModel.displayName) + (groupCount > 1 ? ` (` + groupCount + `)` : ``), default=`Frodo Baggins (6)`}" />
|
||||
android:text="@{(viewModel.isConferenceCallLog ? viewModel.conferenceSubject : viewModel.contact.name ?? viewModel.displayName) + (groupCount > 1 ? ` (` + groupCount + `)` : ``), default=`Frodo Baggins (6)`}" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
android:layout_marginTop="15dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{callsViewModel.currentCallData.contact.fullName ?? callsViewModel.currentCallData.displayName, default=`Bilbo Baggins`}"
|
||||
android:text="@{callsViewModel.currentCallData.contact.name ?? callsViewModel.currentCallData.displayName, default=`Bilbo Baggins`}"
|
||||
app:layout_constraintBottom_toTopOf="@id/textView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
android:layout_marginTop="15dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{callsViewModel.currentCallData.remoteConferenceSubject ?? callsViewModel.currentCallData.contact.fullName ?? callsViewModel.currentCallData.displayName, default=`Bilbo Baggins`}"
|
||||
android:text="@{callsViewModel.currentCallData.remoteConferenceSubject ?? callsViewModel.currentCallData.contact.name ?? callsViewModel.currentCallData.displayName, default=`Bilbo Baggins`}"
|
||||
app:layout_constraintBottom_toTopOf="@id/callee_address"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
style="@style/call_list_active_name_font"
|
||||
android:text="@{data.contact.fullName ?? data.displayName, default=`Bilbo Baggins`}"/>
|
||||
android:text="@{data.contact.name ?? data.displayName, default=`Bilbo Baggins`}"/>
|
||||
|
||||
<TextView
|
||||
android:visibility="@{data.isInRemoteConference ? View.VISIBLE : View.GONE, default=gone}"
|
||||
|
@ -104,7 +104,7 @@
|
|||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
style="@style/call_list_name_font"
|
||||
android:text="@{data.contact.fullName ?? data.displayName, default=`Bilbo Baggins`}"/>
|
||||
android:text="@{data.contact.name ?? data.displayName, default=`Bilbo Baggins`}"/>
|
||||
|
||||
<TextView
|
||||
android:visibility="@{data.isInRemoteConference ? View.VISIBLE : View.GONE, default=gone}"
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
android:orientation="vertical">
|
||||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.fullName ?? viewModel.displayName) : viewModel.subject}"
|
||||
android:text="@{viewModel.oneToOneChatRoom ? (viewModel.contact.name ?? viewModel.displayName) : viewModel.subject}"
|
||||
style="@style/toolbar_small_title_font"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@{conferenceViewModel.speakingParticipant.contact.fullName ?? conferenceViewModel.speakingParticipant.displayName}"
|
||||
android:text="@{conferenceViewModel.speakingParticipant.contact.name ?? conferenceViewModel.speakingParticipant.displayName}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="@{data.contact.fullName ?? data.displayName, default=`Bilbo Baggins`}" />
|
||||
android:text="@{data.contact.name ?? data.displayName, default=`Bilbo Baggins`}" />
|
||||
|
||||
<org.linphone.views.MarqueeTextView
|
||||
android:id="@+id/sipUri"
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="@{data.contact.fullName ?? data.displayName}"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="@{data.contact.fullName ?? data.displayName, default=`Merry Brandybuck`}"
|
||||
android:text="@{data.contact.name ?? data.displayName, default=`Merry Brandybuck`}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/participant_avatar"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:text="@{data.contact.fullName ?? data.displayName}"
|
||||
android:text="@{data.contact.name ?? data.displayName}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
|
|
@ -50,7 +50,16 @@
|
|||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@null"
|
||||
android:background="?attr/voipParticipantBackgroundColor"
|
||||
app:glideAvatar="@{data.contact.contactPictureUri}" />
|
||||
app:glideAvatar="@{data.thumbnailUri}" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentStart="true"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@null"
|
||||
android:background="?attr/voipParticipantBackgroundColor"
|
||||
app:glideAvatar="@{data.pictureUri}" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -54,7 +54,16 @@
|
|||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@null"
|
||||
android:background="?attr/voipBackgroundColor"
|
||||
app:glideAvatar="@{data.contact.contactPictureUri}" />
|
||||
app:glideAvatar="@{data.thumbnailUri}" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentStart="true"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@null"
|
||||
android:background="?attr/voipBackgroundColor"
|
||||
app:glideAvatar="@{data.pictureUri}" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@{(callsViewModel.currentCallData.contact.fullName ?? callsViewModel.currentCallData.displayName) + ` - `, default=`John Doe - `}"
|
||||
android:text="@{(callsViewModel.currentCallData.contact.name ?? callsViewModel.currentCallData.displayName) + ` - `, default=`John Doe - `}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
@ -126,7 +126,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@{callsViewModel.currentCallData.contact.fullName ?? callsViewModel.currentCallData.displayName}"
|
||||
android:text="@{callsViewModel.currentCallData.contact.name ?? callsViewModel.currentCallData.displayName}"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background"
|
||||
app:layout_constraintStart_toStartOf="@id/background" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue