From 4f6c5c7f48b819aedd39e6b637d9f48d75c2e393 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 20 May 2024 09:00:33 +0200 Subject: [PATCH] Added audio device picker in advanced settings --- .../fragment/SettingsAdvancedFragment.kt | 55 +++++++++++ .../settings/viewmodel/SettingsViewModel.kt | 87 ++++++++++++++++++ .../main/res/layout/generic_dropdown_cell.xml | 2 + .../res/layout/settings_advanced_fragment.xml | 91 ++++++++++++++++++- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 6 files changed, 237 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt index 91af18f82..01cd89c87 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/fragment/SettingsAdvancedFragment.kt @@ -23,8 +23,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider +import org.linphone.R import org.linphone.databinding.SettingsAdvancedFragmentBinding import org.linphone.ui.main.fragment.GenericMainFragment import org.linphone.ui.main.settings.viewmodel.SettingsViewModel @@ -39,6 +42,24 @@ class SettingsAdvancedFragment : GenericMainFragment() { private lateinit var viewModel: SettingsViewModel + private val inputAudioDeviceDropdownListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + viewModel.setInputAudioDevice(position) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + + private val outputAudioDeviceDropdownListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + viewModel.setOutputAudioDevice(position) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -62,6 +83,14 @@ class SettingsAdvancedFragment : GenericMainFragment() { goBack() } + viewModel.inputAudioDeviceIndex.observe(viewLifecycleOwner) { + setupInputAudioDevicePicker() + } + + viewModel.outputAudioDeviceIndex.observe(viewLifecycleOwner) { + setupOutputAudioDevicePicker() + } + startPostponedEnterTransition() } @@ -70,4 +99,30 @@ class SettingsAdvancedFragment : GenericMainFragment() { super.onPause() } + + private fun setupInputAudioDevicePicker() { + val index = viewModel.inputAudioDeviceIndex.value ?: 0 + val adapter = ArrayAdapter( + requireContext(), + R.layout.drop_down_item, + viewModel.inputAudioDeviceLabels + ) + adapter.setDropDownViewResource(R.layout.generic_dropdown_cell) + binding.inputAudioDevice.adapter = adapter + binding.inputAudioDevice.onItemSelectedListener = inputAudioDeviceDropdownListener + binding.inputAudioDevice.setSelection(index) + } + + private fun setupOutputAudioDevicePicker() { + val index = viewModel.outputAudioDeviceIndex.value ?: 0 + val adapter = ArrayAdapter( + requireContext(), + R.layout.drop_down_item, + viewModel.outputAudioDeviceLabels + ) + adapter.setDropDownViewResource(R.layout.generic_dropdown_cell) + binding.outputAudioDevice.adapter = adapter + binding.outputAudioDevice.onItemSelectedListener = outputAudioDeviceDropdownListener + binding.outputAudioDevice.setSelection(index) + } } diff --git a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt index a36d244ab..5fafc1308 100644 --- a/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/settings/viewmodel/SettingsViewModel.kt @@ -21,11 +21,15 @@ package org.linphone.ui.main.settings.viewmodel import android.os.Vibrator import androidx.annotation.UiThread +import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.R +import org.linphone.core.AudioDevice import org.linphone.core.Conference +import org.linphone.core.Core +import org.linphone.core.CoreListenerStub import org.linphone.core.FriendList import org.linphone.core.VFS import org.linphone.core.tools.Log @@ -148,8 +152,27 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() { val remoteProvisioningUrl = MutableLiveData() + val inputAudioDeviceIndex = MutableLiveData() + val inputAudioDeviceLabels = arrayListOf() + private val inputAudioDeviceValues = arrayListOf() + + val outputAudioDeviceIndex = MutableLiveData() + val outputAudioDeviceLabels = arrayListOf() + private val outputAudioDeviceValues = arrayListOf() + + private val coreListener = object : CoreListenerStub() { + override fun onAudioDevicesListUpdated(core: Core) { + Log.i( + "$TAG Audio devices list has changed, update available input/output audio devices list" + ) + setupAudioDevices() + } + } + init { coreContext.postOnCoreThread { core -> + core.addListener(coreListener) + showConversationsSettings.postValue(!corePreferences.disableChat) showMeetingsSettings.postValue(!corePreferences.disableMeetings) ldapAvailable.postValue(core.ldapAvailable()) @@ -197,9 +220,19 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() { keepAliveThirdPartyAccountsService.postValue(corePreferences.keepServiceAlive) remoteProvisioningUrl.postValue(core.provisioningUri) + + setupAudioDevices() } } + override fun onCleared() { + coreContext.postOnCoreThread { core -> + core.removeListener(coreListener) + } + + super.onCleared() + } + @UiThread fun toggleSecurityExpand() { expandSecurity.value = expandSecurity.value == false @@ -463,4 +496,58 @@ class SettingsViewModel @UiThread constructor() : GenericViewModel() { Log.i("$TAG Core has been restarted") } } + + @UiThread + fun setInputAudioDevice(index: Int) { + coreContext.postOnCoreThread { core -> + val audioDevice = inputAudioDeviceValues[index] + core.defaultInputAudioDevice = audioDevice + } + } + + @UiThread + fun setOutputAudioDevice(index: Int) { + coreContext.postOnCoreThread { core -> + val audioDevice = outputAudioDeviceValues[index] + core.defaultOutputAudioDevice = audioDevice + } + } + + @WorkerThread + private fun setupAudioDevices() { + val core = coreContext.core + + inputAudioDeviceLabels.clear() + inputAudioDeviceValues.clear() + outputAudioDeviceLabels.clear() + outputAudioDeviceValues.clear() + + var inputIndex = 0 + val defaultInputAudioDevice = core.defaultInputAudioDevice + Log.i("$TAG Current default input audio device is [${defaultInputAudioDevice?.id}]") + for (audioDevice in core.extendedAudioDevices) { + if (audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityRecord)) { + inputAudioDeviceLabels.add(audioDevice.id) + inputAudioDeviceValues.add(audioDevice) + if (audioDevice.id == defaultInputAudioDevice?.id) { + inputAudioDeviceIndex.postValue(inputIndex) + } + inputIndex += 1 + } + } + + var outputIndex = 0 + val defaultOutputAudioDevice = core.defaultOutputAudioDevice + Log.i("$TAG Current default output audio device is [${defaultOutputAudioDevice?.id}]") + for (audioDevice in core.extendedAudioDevices) { + if (audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { + outputAudioDeviceLabels.add(audioDevice.id) + outputAudioDeviceValues.add(audioDevice) + if (audioDevice.id == defaultOutputAudioDevice?.id) { + outputAudioDeviceIndex.postValue(outputIndex) + } + outputIndex += 1 + } + } + } } diff --git a/app/src/main/res/layout/generic_dropdown_cell.xml b/app/src/main/res/layout/generic_dropdown_cell.xml index 3dce72423..f29a33578 100644 --- a/app/src/main/res/layout/generic_dropdown_cell.xml +++ b/app/src/main/res/layout/generic_dropdown_cell.xml @@ -9,4 +9,6 @@ android:gravity="center_vertical" android:paddingStart="20dp" android:paddingEnd="20dp" + android:maxLines="1" + android:ellipsize="end" tools:ignore="HardcodedText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/settings_advanced_fragment.xml b/app/src/main/res/layout/settings_advanced_fragment.xml index 9a984ec4b..9fd5875c7 100644 --- a/app/src/main/res/layout/settings_advanced_fragment.xml +++ b/app/src/main/res/layout/settings_advanced_fragment.xml @@ -103,7 +103,6 @@ android:layout_width="0dp" android:layout_height="50dp" android:layout_marginEnd="16dp" - android:layout_marginTop="5dp" android:paddingStart="20dp" android:paddingEnd="20dp" android:text="@={viewModel.remoteProvisioningUrl}" @@ -121,7 +120,7 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 87291ba13..e9902f875 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -278,6 +278,8 @@ Garder l\'app en vie via un Service URL de configuration distante Télécharger & appliquer + Périphérique de capture par défaut + Périphérique d\'écoute par défaut Votre compte diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af99344c4..1493ad15d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -313,6 +313,8 @@ Keep app alive using Service Remote provisioning URL Download & apply + Default input audio device + Default output audio device Manage account