Added audio device picker in advanced settings

This commit is contained in:
Sylvain Berfini 2024-05-20 09:00:33 +02:00
parent d48f7697df
commit 4f6c5c7f48
6 changed files with 237 additions and 2 deletions

View file

@ -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)
}
}

View file

@ -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<String>()
val inputAudioDeviceIndex = MutableLiveData<Int>()
val inputAudioDeviceLabels = arrayListOf<String>()
private val inputAudioDeviceValues = arrayListOf<AudioDevice>()
val outputAudioDeviceIndex = MutableLiveData<Int>()
val outputAudioDeviceLabels = arrayListOf<String>()
private val outputAudioDeviceValues = arrayListOf<AudioDevice>()
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
}
}
}
}

View file

@ -9,4 +9,6 @@
android:gravity="center_vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:maxLines="1"
android:ellipsize="end"
tools:ignore="HardcodedText" />

View file

@ -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 @@
<androidx.appcompat.widget.AppCompatTextView
style="@style/tertiary_button_label_style"
android:id="@+id/show_config_file"
android:id="@+id/download_and_apply"
android:onClick="@{() -> viewModel.downloadAndApplyRemoteProvisioning()}"
android:enabled="@{viewModel.remoteProvisioningUrl.length() != 0, default=false}"
android:layout_width="wrap_content"
@ -141,6 +140,94 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/remote_provisioning"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/input_audio_device_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_advanced_input_audio_device_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/download_and_apply"/>
<androidx.appcompat.widget.AppCompatSpinner
style="@style/default_text_style"
android:id="@+id/input_audio_device"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:gravity="center_vertical"
android:overlapAnchor="false"
android:dropDownVerticalOffset="25dp"
android:spinnerMode="dropdown"
android:popupBackground="@drawable/shape_squircle_white_background"
android:background="@drawable/edit_text_background"
app:layout_constraintTop_toBottomOf="@id/input_audio_device_label"
app:layout_constraintStart_toStartOf="@id/input_audio_device_label"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/input_audio_device_caret"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/caret_down"
android:contentDescription="@null"
app:layout_constraintTop_toTopOf="@id/input_audio_device"
app:layout_constraintBottom_toBottomOf="@id/input_audio_device"
app:layout_constraintEnd_toEndOf="@id/input_audio_device"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/header_style"
android:id="@+id/output_audio_device_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="@string/settings_advanced_output_audio_device_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/input_audio_device"/>
<androidx.appcompat.widget.AppCompatSpinner
style="@style/default_text_style"
android:id="@+id/output_audio_device"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="14sp"
android:textColor="@color/gray_main2_600"
android:gravity="center_vertical"
android:overlapAnchor="false"
android:dropDownVerticalOffset="25dp"
android:spinnerMode="dropdown"
android:popupBackground="@drawable/shape_squircle_white_background"
android:background="@drawable/edit_text_background"
app:layout_constraintTop_toBottomOf="@id/output_audio_device_label"
app:layout_constraintStart_toStartOf="@id/output_audio_device_label"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/output_audio_device_caret"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/caret_down"
android:contentDescription="@null"
app:layout_constraintTop_toTopOf="@id/output_audio_device"
app:layout_constraintBottom_toBottomOf="@id/output_audio_device"
app:layout_constraintEnd_toEndOf="@id/output_audio_device"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -278,6 +278,8 @@
<string name="settings_advanced_keep_alive_service_title">Garder l\'app en vie via un Service</string>
<string name="settings_advanced_remote_provisioning_url">URL de configuration distante</string>
<string name="settings_advanced_download_apply_remote_provisioning">Télécharger &amp; appliquer</string>
<string name="settings_advanced_input_audio_device_title">Périphérique de capture par défaut</string>
<string name="settings_advanced_output_audio_device_title">Périphérique d\'écoute par défaut</string>
<!-- Account profile & settings -->
<string name="manage_account_title">Votre compte</string>

View file

@ -313,6 +313,8 @@
<string name="settings_advanced_keep_alive_service_title">Keep app alive using Service</string>
<string name="settings_advanced_remote_provisioning_url">Remote provisioning URL</string>
<string name="settings_advanced_download_apply_remote_provisioning">Download &amp; apply</string>
<string name="settings_advanced_input_audio_device_title">Default input audio device</string>
<string name="settings_advanced_output_audio_device_title">Default output audio device</string>
<!-- Account profile & settings -->
<string name="manage_account_title">Manage account</string>