diff --git a/CHANGELOG.md b/CHANGELOG.md index 20ac29e20..6d7e3fb97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,18 @@ Group changes to describe their impact on the project, as follows: Fixed for any bug fixes. Security to invite users to upgrade in case of vulnerabilities. - ## [4.7.0] - Unreleased -## [4.6.2] - Unreleased +### Added +- + +### Changed +- + +### Fixed +- + +## [4.6.2] - 2022-03-01 ### Added - Request BLUETOOTH_CONNECT permission on Android 12+ devices, if not we won't be notified when a BT device is being connected/disconnected while app is alive. @@ -23,13 +31,15 @@ Group changes to describe their impact on the project, as follows: - Prevent screen to turn off while recording a voice message ### Changed -- Contacts lists now show LDAP contacts if any, as well as "generated" contacts from SIP addresses you have interacted with +- Contacts lists now show LDAP contacts if any ### Fixed - Negative gain in audio settings is allowed again - STUN server URL setting not enabling it for non sip.linphone.org accounts - Contacts list header case comparison - Stop voice recording playback when sending chat message +- Call activity not finishing when hanging up sometimes +- Auto start setting disabled not working if background mode setting was enabled ## [4.6.1] - 2022-02-14 diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 331c2417f..a5239baad 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -866,6 +866,17 @@ internal fun navigateToEmptySetting(navController: NavController) { ) } +internal fun ContactsSettingsFragment.navigateToLdapSettings(configIndex: Int) { + if (findNavController().currentDestination?.id == R.id.contactsSettingsFragment) { + val bundle = bundleOf("LdapConfigIndex" to configIndex) + findNavController().navigate( + R.id.action_contactsSettingsFragment_to_ldapSettingsFragment, + bundle, + popupTo() + ) + } +} + internal fun AccountSettingsFragment.navigateToEmptySetting() { navigateToEmptySetting(findNavController()) } diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt index 540dad03c..92cccd3be 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt @@ -36,6 +36,7 @@ import com.google.android.material.transition.MaterialSharedAxis import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R +import org.linphone.activities.SnackBarActivity import org.linphone.activities.clearDisplayedContact import org.linphone.activities.main.MainActivity import org.linphone.activities.main.contact.adapters.ContactsListAdapter @@ -172,6 +173,14 @@ class MasterContactsFragment : MasterFragment Log.i("[Contacts] Selected item in list changed: $contact") sharedViewModel.selectedContact.value = contact + (requireActivity() as MainActivity).hideKeyboard() if (editOnClick) { navigateToContactEditor(sipUriToAdd, binding.slidingPane) @@ -237,6 +247,14 @@ class MasterContactsFragment : MasterFragment() + val isNativeContact = MutableLiveData() + private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { override fun onContactUpdated(contact: Contact) { if (contact is NativeContact && contactInternal is NativeContact && contact.nativeId == contactInternal.nativeId) { @@ -127,6 +129,7 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel( init { contact.value = contactInternal displayName.value = contactInternal.fullName ?: contactInternal.firstName + " " + contactInternal.lastName + isNativeContact.value = contactInternal is NativeContact updateNumbersAndAddresses(contactInternal) coreContext.contactsManager.addListener(contactsUpdatedListener) @@ -172,7 +175,7 @@ class ContactViewModel(val contactInternal: Contact) : MessageNotifierViewModel( } } - private fun updateNumbersAndAddresses(contact: Contact) { + fun updateNumbersAndAddresses(contact: Contact) { val list = arrayListOf() for (address in contact.sipAddresses) { val value = address.asStringUriOnly() diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt index cf4bb3331..44d65a981 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt @@ -23,61 +23,125 @@ import android.content.ContentProviderOperation import android.provider.ContactsContract import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import java.util.* +import kotlinx.coroutines.* import org.linphone.LinphoneApplication.Companion.coreContext 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 class ContactsListViewModel : ViewModel() { val sipContactsSelected = MutableLiveData() val contactsList = MutableLiveData>() + val fetchInProgress = MutableLiveData() + private var searchResultsPending: Boolean = false + private var fastFetchJob: Job? = null + val filter = MutableLiveData() + private var previousFilter = "NotSet" + + val moreResultsAvailableEvent: MutableLiveData> by lazy { + MutableLiveData>() + } private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { override fun onContactsUpdated() { Log.i("[Contacts] Contacts have changed") - updateContactsList() + updateContactsList(true) + } + } + + private val magicSearchListener = object : MagicSearchListenerStub() { + override fun onSearchResultsReceived(magicSearch: MagicSearch) { + searchResultsPending = false + processMagicSearchResults(magicSearch.lastSearch) + fetchInProgress.value = false + } + + override fun onLdapHaveMoreResults(magicSearch: MagicSearch, ldap: Ldap) { + moreResultsAvailableEvent.value = Event(true) } } init { sipContactsSelected.value = coreContext.contactsManager.shouldDisplaySipContactsList() + fetchInProgress.value = false coreContext.contactsManager.addListener(contactsUpdatedListener) + coreContext.contactsManager.magicSearch.addListener(magicSearchListener) } override fun onCleared() { contactsList.value.orEmpty().forEach(ContactViewModel::destroy) + coreContext.contactsManager.magicSearch.removeListener(magicSearchListener) coreContext.contactsManager.removeListener(contactsUpdatedListener) super.onCleared() } - private fun getSelectedContactsList(): ArrayList { - val list = arrayListOf() - val source = - if (sipContactsSelected.value == true) coreContext.contactsManager.sipContacts - else coreContext.contactsManager.contacts - for (contact in source) { - list.add(ContactViewModel(contact)) + fun updateContactsList(clearCache: Boolean) { + val filterValue = filter.value.orEmpty() + contactsList.value.orEmpty().forEach(ContactViewModel::destroy) + + if (clearCache || ( + previousFilter.isNotEmpty() && ( + previousFilter.length > filterValue.length || + (previousFilter.length == filterValue.length && previousFilter != filterValue) + ) + ) + ) { + coreContext.contactsManager.magicSearch.resetSearchCache() + } + previousFilter = filterValue + + val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else "" + val filter = MagicSearchSource.Friends.toInt() or MagicSearchSource.LdapServers.toInt() + searchResultsPending = true + fastFetchJob?.cancel() + coreContext.contactsManager.magicSearch.getContactsAsync(filterValue, domain, filter) + + fastFetchJob = viewModelScope.launch { + withContext(Dispatchers.IO) { + delay(200) + withContext(Dispatchers.Main) { + if (searchResultsPending) { + fetchInProgress.value = true + } + } + } } - return list } - fun updateContactsList() { - val list: ArrayList - - val filterValue = filter.value.orEmpty() - list = if (filterValue.isNotEmpty()) { - getSelectedContactsList().filter { contact -> - contact.name.contains(filterValue, true) - } as ArrayList - } else { - getSelectedContactsList() + private fun processMagicSearchResults(results: Array) { + val list = arrayListOf() + 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)) } contactsList.postValue(list) @@ -146,4 +210,19 @@ class ContactsListViewModel : ViewModel() { } } } + + private fun searchMatchingContact(searchResult: SearchResult): Contact? { + val address = searchResult.address + + if (address != null) { + val contact = coreContext.contactsManager.findContactByAddress(address, ignoreLocalContact = true) + if (contact != null) return contact + } + + if (searchResult.phoneNumber != null) { + return coreContext.contactsManager.findContactByPhoneNumber(searchResult.phoneNumber.orEmpty()) + } + + return null + } } diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt index 4d89df1a4..b5d0bc557 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt @@ -26,8 +26,10 @@ import androidx.lifecycle.ViewModelProvider import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R +import org.linphone.activities.main.settings.SettingListenerStub import org.linphone.activities.main.settings.viewmodels.ContactsSettingsViewModel import org.linphone.activities.navigateToEmptySetting +import org.linphone.activities.navigateToLdapSettings import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log import org.linphone.databinding.SettingsContactsFragmentBinding @@ -74,6 +76,22 @@ class ContactsSettingsFragment : GenericSettingFragment + Log.i("[Contacts Settings] Clicked on LDAP config with index: $index") + navigateToLdapSettings(index) + } + } + if (!PermissionHelper.required(requireContext()).hasReadContactsPermission()) { Log.i("[Contacts Settings] Asking for READ_CONTACTS permission") requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) @@ -117,4 +135,9 @@ class ContactsSettingsFragment : GenericSettingFragment. + */ +package org.linphone.activities.main.settings.fragments + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import org.linphone.R +import org.linphone.activities.main.settings.viewmodels.LdapSettingsViewModel +import org.linphone.activities.main.settings.viewmodels.LdapSettingsViewModelFactory +import org.linphone.core.tools.Log +import org.linphone.databinding.SettingsLdapFragmentBinding + +class LdapSettingsFragment : GenericSettingFragment() { + private lateinit var viewModel: LdapSettingsViewModel + + override fun getLayoutId(): Int = R.layout.settings_ldap_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.lifecycleOwner = viewLifecycleOwner + binding.sharedMainViewModel = sharedViewModel + + val configIndex = arguments?.getInt("LdapConfigIndex") + if (configIndex == null) { + Log.e("[LDAP Settings] Config index not specified!") + goBack() + return + } + + try { + viewModel = ViewModelProvider(this, LdapSettingsViewModelFactory(configIndex))[LdapSettingsViewModel::class.java] + } catch (nsee: NoSuchElementException) { + Log.e("[LDAP Settings] Failed to find LDAP object, aborting!") + goBack() + return + } + binding.viewModel = viewModel + + viewModel.ldapConfigDeletedEvent.observe( + viewLifecycleOwner + ) { + it.consume { + goBack() + } + } + + binding.setBackClickListener { goBack() } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt index 41aac8d87..6b56265d0 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt @@ -20,6 +20,7 @@ package org.linphone.activities.main.settings.viewmodels import androidx.lifecycle.MutableLiveData +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.activities.main.settings.SettingListenerStub import org.linphone.utils.Event import org.linphone.utils.PermissionHelper @@ -76,6 +77,20 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() { val launcherShortcuts = MutableLiveData() val launcherShortcutsEvent = MutableLiveData>() + val ldapAvailable = MutableLiveData() + + val ldapConfigurations = MutableLiveData>() + + lateinit var ldapNewSettingsListener: SettingListenerStub + val ldapSettingsClickedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + private var ldapSettingsListener = object : SettingListenerStub() { + override fun onAccountClicked(identity: String) { + ldapSettingsClickedEvent.value = Event(identity.toInt()) + } + } + init { readContactsPermissionGranted.value = PermissionHelper.get().hasReadContactsPermission() @@ -84,5 +99,23 @@ class ContactsSettingsViewModel : GenericSettingsViewModel() { nativePresence.value = prefs.storePresenceInNativeContact showOrganization.value = prefs.displayOrganization launcherShortcuts.value = prefs.contactsShortcuts + + ldapAvailable.value = core.ldapAvailable() + ldapConfigurations.value = arrayListOf() + + updateLdapConfigurationsList() + } + + fun updateLdapConfigurationsList() { + val list = arrayListOf() + var index = 0 + for (ldap in coreContext.core.ldapList) { + val viewModel = LdapSettingsViewModel(ldap, index.toString()) + viewModel.ldapSettingsListener = ldapSettingsListener + list.add(viewModel) + index += 1 + } + + ldapConfigurations.value = list } } diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt new file mode 100644 index 000000000..ab6abcf15 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2010-2022 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 . + */ +package org.linphone.activities.main.settings.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import java.lang.NumberFormatException +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R +import org.linphone.activities.main.settings.SettingListenerStub +import org.linphone.core.Ldap +import org.linphone.core.LdapAuthMethod +import org.linphone.core.LdapCertVerificationMode +import org.linphone.core.LdapDebugLevel +import org.linphone.core.tools.Log +import org.linphone.utils.Event + +class LdapSettingsViewModelFactory(private val index: Int) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + if (index >= 0 && index <= coreContext.core.ldapList.size) { + val ldap = coreContext.core.ldapList[index] + return LdapSettingsViewModel(ldap, index.toString()) as T + } + + val ldapParams = coreContext.core.createLdapParams() + val ldap = coreContext.core.createLdapWithParams(ldapParams) + return LdapSettingsViewModel(ldap, "-1") as T + } +} + +class LdapSettingsViewModel(private val ldap: Ldap, val index: String) : GenericSettingsViewModel() { + lateinit var ldapSettingsListener: SettingListenerStub + + val ldapConfigDeletedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + + val ldapEnableListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + val params = ldap.params.clone() + params.enabled = newValue + ldap.params = params + } + } + val ldapEnable = MutableLiveData() + + val deleteListener = object : SettingListenerStub() { + override fun onClicked() { + coreContext.core.removeLdap(ldap) + ldapConfigDeletedEvent.value = Event(true) + } + } + + val ldapServerListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.server = newValue + ldap.params = params + } + } + val ldapServer = MutableLiveData() + + val ldapBindDnListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.bindDn = newValue + ldap.params = params + } + } + val ldapBindDn = MutableLiveData() + + val ldapPasswordListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.password = newValue + ldap.params = params + } + } + val ldapPassword = MutableLiveData() + + val ldapAuthMethodListener = object : SettingListenerStub() { + override fun onListValueChanged(position: Int) { + val params = ldap.params.clone() + params.authMethod = LdapAuthMethod.fromInt(ldapAuthMethodValues[position]) + ldap.params = params + ldapAuthMethodIndex.value = position + } + } + val ldapAuthMethodIndex = MutableLiveData() + val ldapAuthMethodLabels = MutableLiveData>() + private val ldapAuthMethodValues = arrayListOf() + + val ldapTlsListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + val params = ldap.params.clone() + params.isTlsEnabled = newValue + ldap.params = params + } + } + val ldapTls = MutableLiveData() + + val ldapCertCheckListener = object : SettingListenerStub() { + override fun onListValueChanged(position: Int) { + val params = ldap.params.clone() + params.serverCertificatesVerificationMode = LdapCertVerificationMode.fromInt(ldapCertCheckValues[position]) + ldap.params = params + ldapCertCheckIndex.value = position + } + } + val ldapCertCheckIndex = MutableLiveData() + val ldapCertCheckLabels = MutableLiveData>() + private val ldapCertCheckValues = arrayListOf() + + val ldapSearchBaseListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.baseObject = newValue + ldap.params = params + } + } + val ldapSearchBase = MutableLiveData() + + val ldapSearchFilterListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.filter = newValue + ldap.params = params + } + } + val ldapSearchFilter = MutableLiveData() + + val ldapSearchMaxResultsListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + try { + val intValue = newValue.toInt() + val params = ldap.params.clone() + params.maxResults = intValue + ldap.params = params + } catch (nfe: NumberFormatException) { + Log.e("[LDAP Settings] Failed to set max results ($newValue): $nfe") + } + } + } + val ldapSearchMaxResults = MutableLiveData() + + val ldapSearchTimeoutListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + try { + val intValue = newValue.toInt() + val params = ldap.params.clone() + params.timeout = intValue + ldap.params = params + } catch (nfe: NumberFormatException) { + Log.e("[LDAP Settings] Failed to set timeout ($newValue): $nfe") + } + } + } + val ldapSearchTimeout = MutableLiveData() + + val ldapRequestDelayListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + try { + val intValue = newValue.toInt() + val params = ldap.params.clone() + params.delay = intValue + ldap.params = params + } catch (nfe: NumberFormatException) { + Log.e("[LDAP Settings] Failed to set request delay ($newValue): $nfe") + } + } + } + val ldapRequestDelay = MutableLiveData() + + val ldapMinimumCharactersListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + try { + val intValue = newValue.toInt() + val params = ldap.params.clone() + params.minChars = intValue + ldap.params = params + } catch (nfe: NumberFormatException) { + Log.e("[LDAP Settings] Failed to set minimum characters ($newValue): $nfe") + } + } + } + val ldapMinimumCharacters = MutableLiveData() + + val ldapNameAttributeListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.nameAttribute = newValue + ldap.params = params + } + } + val ldapNameAttribute = MutableLiveData() + + val ldapSipAttributeListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.sipAttribute = newValue + ldap.params = params + } + } + val ldapSipAttribute = MutableLiveData() + + val ldapSipDomainListener = object : SettingListenerStub() { + override fun onTextValueChanged(newValue: String) { + val params = ldap.params.clone() + params.sipDomain = newValue + ldap.params = params + } + } + val ldapSipDomain = MutableLiveData() + + val ldapDebugListener = object : SettingListenerStub() { + override fun onBoolValueChanged(newValue: Boolean) { + val params = ldap.params.clone() + params.debugLevel = if (newValue) LdapDebugLevel.Verbose else LdapDebugLevel.Off + ldap.params = params + } + } + val ldapDebug = MutableLiveData() + + init { + val params = ldap.params + + ldapEnable.value = params.enabled + ldapServer.value = params.server + ldapBindDn.value = params.bindDn + ldapPassword.value = params.password + ldapTls.value = params.isTlsEnabled + ldapSearchBase.value = params.baseObject + ldapSearchFilter.value = params.filter + ldapSearchMaxResults.value = params.maxResults + ldapSearchTimeout.value = params.timeout + ldapRequestDelay.value = params.delay + ldapMinimumCharacters.value = params.minChars + ldapNameAttribute.value = params.nameAttribute + ldapSipAttribute.value = params.sipAttribute + ldapSipDomain.value = params.sipDomain + ldapDebug.value = params.debugLevel == LdapDebugLevel.Verbose + + initAuthMethodList() + initTlsCertCheckList() + } + + private fun initAuthMethodList() { + val labels = arrayListOf() + + labels.add(prefs.getString(R.string.contacts_settings_ldap_auth_method_anonymous)) + ldapAuthMethodValues.add(LdapAuthMethod.Anonymous.toInt()) + + labels.add(prefs.getString(R.string.contacts_settings_ldap_auth_method_simple)) + ldapAuthMethodValues.add(LdapAuthMethod.Simple.toInt()) + + ldapAuthMethodLabels.value = labels + ldapAuthMethodIndex.value = ldapAuthMethodValues.indexOf(ldap.params.authMethod.toInt()) + } + + private fun initTlsCertCheckList() { + val labels = arrayListOf() + + labels.add(prefs.getString(R.string.contacts_settings_ldap_cert_check_auto)) + ldapCertCheckValues.add(LdapCertVerificationMode.Default.toInt()) + + labels.add(prefs.getString(R.string.contacts_settings_ldap_cert_check_disabled)) + ldapCertCheckValues.add(LdapCertVerificationMode.Disabled.toInt()) + + labels.add(prefs.getString(R.string.contacts_settings_ldap_cert_check_enabled)) + ldapCertCheckValues.add(LdapCertVerificationMode.Enabled.toInt()) + + ldapCertCheckLabels.value = labels + ldapCertCheckIndex.value = ldapCertCheckValues.indexOf(ldap.params.serverCertificatesVerificationMode.toInt()) + } +} diff --git a/app/src/main/java/org/linphone/contact/Contact.kt b/app/src/main/java/org/linphone/contact/Contact.kt index 64ad43bf8..f556f8961 100644 --- a/app/src/main/java/org/linphone/contact/Contact.kt +++ b/app/src/main/java/org/linphone/contact/Contact.kt @@ -29,8 +29,10 @@ 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 { override fun compareTo(other: PhoneNumber): Int { @@ -38,7 +40,7 @@ data class PhoneNumber(val value: String, val typeLabel: String) : Comparable { +open class Contact() : Comparable { var fullName: String? = null var firstName: String? = null var lastName: String? = null @@ -55,6 +57,31 @@ open class Contact : Comparable { private var thumbnailUri: Uri? = null + constructor(searchResult: SearchResult) : this() { + friend = searchResult.friend + 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() + } + + phoneNumbers.add(PhoneNumber(phoneNumber, "")) + } + } + override fun compareTo(other: Contact): Int { val fn = fullName ?: "" val otherFn = other.fullName ?: "" diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt index 7bb7658ea..94006ceaf 100644 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contact/ContactsManager.kt @@ -238,13 +238,15 @@ class ContactsManager(private val context: Context) { } @Synchronized - fun findContactByAddress(address: Address): Contact? { - val localContact = localAccountsContacts.find { localContact -> - localContact.sipAddresses.find { localAddress -> - address.weakEqual(localAddress) - } != null + 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 } - if (localContact != null) return localContact val cleanAddress = address.clone() cleanAddress.clean() // To remove gruu if any diff --git a/app/src/main/res/layout/contact_detail_fragment.xml b/app/src/main/res/layout/contact_detail_fragment.xml index 56a26aa3d..0d0263a7b 100644 --- a/app/src/main/res/layout/contact_detail_fragment.xml +++ b/app/src/main/res/layout/contact_detail_fragment.xml @@ -52,6 +52,7 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/settings_ldap_cell.xml b/app/src/main/res/layout/settings_ldap_cell.xml new file mode 100644 index 000000000..510114c5e --- /dev/null +++ b/app/src/main/res/layout/settings_ldap_cell.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_ldap_fragment.xml b/app/src/main/res/layout/settings_ldap_fragment.xml new file mode 100644 index 000000000..3005446a8 --- /dev/null +++ b/app/src/main/res/layout/settings_ldap_fragment.xml @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/settings_nav_graph.xml b/app/src/main/res/navigation/settings_nav_graph.xml index 1da8f94e0..be8927f31 100644 --- a/app/src/main/res/navigation/settings_nav_graph.xml +++ b/app/src/main/res/navigation/settings_nav_graph.xml @@ -49,7 +49,11 @@ android:id="@+id/contactsSettingsFragment" android:name="org.linphone.activities.main.settings.fragments.ContactsSettingsFragment" tools:layout="@layout/settings_contacts_fragment" - android:label="ContactsSettingsFragment" /> + android:label="ContactsSettingsFragment" > + + + Contribuer aux traductions Certaines fonctionnalités avancées comme les messages de groupe ou les messages éphémères nécessitent un compte &appName;.\n\nElles seront masquées dans l\'application si vous configurez un compte SIP tiers.\n\nSi vous souhaitez les activer pour un projet professionnel, contactez-nous. J\'ai compris + Nouvelle configuration LDAP + LDAP + Activer + Supprimer + Connexion + URL du serveur + Bind DN + Mot de passe + Méthode d\'authentification + Anonyme + Simple + Utiliser TLS + Vérifier les certificats + Auto + Désactivé + Activé + Recherche + Base de recherche + Ne doit pas être vide ! + Filtre + Résultats maximum + Durée max + En secondes + Délai entre 2 requêtes + En millisecondes + Nombre minimum de caractères pour lancer la requête + Analyse + Attributs de nom + Attributs SIP + Domaine + Divers + Débogage + Ce contact ne peut être supprimé + Plus de résultats sont disponibles, affinez votre recherche \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b04279dd5..a145ae7f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,6 +111,8 @@ Choose where to save the contact Store locally + This contact can\'t be deleted + More results are available, refine your search Enter a number or an address @@ -543,6 +545,40 @@ Will replace chat room shortcuts if any Always ask in which account save newly created contact If disabled contact will be stored locally + New LDAP configuration + + + LDAP + Enable + Delete + Connection + Server URL + Bind DN + Password + Authentication method + Anonymous + Simple + Use TLS + Check certificates + Auto + Disabled + Enabled + Search + Search base + Must not be empty! + Filter + Max results + Timeout + In seconds + Delay between two requests + In milliseconds + Minimum characters to start query + Parsing + Name attributes + SIP attributes + Domain + Misc + Debug Debug logs