diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 5d6bd190a..e5b69c538 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -711,6 +711,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/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..4e2dec957 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt @@ -0,0 +1,266 @@ +/* + * 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 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 + 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/res/layout/settings_contacts_fragment.xml b/app/src/main/res/layout/settings_contacts_fragment.xml index ad9544ed9..6a75622f3 100644 --- a/app/src/main/res/layout/settings_contacts_fragment.xml +++ b/app/src/main/res/layout/settings_contacts_fragment.xml @@ -107,6 +107,35 @@ linphone:checked="@={viewModel.launcherShortcuts}" linphone:enabled="@{viewModel.readContactsPermissionGranted}"/> + + + + + + + + + + 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..237c1cd8e --- /dev/null +++ b/app/src/main/res/layout/settings_ldap_fragment.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 3e226bf78..36a176bd2 100644 --- a/app/src/main/res/navigation/settings_nav_graph.xml +++ b/app/src/main/res/navigation/settings_nav_graph.xml @@ -50,7 +50,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" > + + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index df0cff122..3f0c59e24 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -624,4 +624,32 @@ 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 + 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 + Analyse + Attributs de nom + Attributs SIP + Domaine + Divers + Débogage \ 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 d1782128e..acc871fca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -483,6 +483,37 @@ 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 + Parsing + Name attributes + SIP attributes + Domain + Misc + Debug Debug logs