Make audio device button directly toggle when only two devices are present.

This commit is contained in:
Nicholas
2023-04-24 11:56:58 -04:00
committed by Alex Hart
parent 634e4abcc1
commit 125c4f43cf
11 changed files with 274 additions and 188 deletions

View File

@@ -832,7 +832,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@RequiresApi(31)
@Override
public void onAudioOutputChanged31(@NonNull int audioDeviceInfo) {
public void onAudioOutputChanged31(@NonNull Integer audioDeviceInfo) {
ApplicationDependencies.getSignalCallManager().selectAudioDevice(new SignalAudioManager.ChosenAudioDeviceIdentifier(audioDeviceInfo));
}

View File

@@ -57,7 +57,7 @@ final class AudioOutputAdapter extends RecyclerView.Adapter<AudioOutputAdapter.V
if (mode != selected) {
setSelectedOutput(mode);
onAudioOutputChangedListener.audioOutputChanged(selected);
onAudioOutputChangedListener.audioOutputChanged(new WebRtcAudioDevice(selected, null));
}
}

View File

@@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.components.webrtc
/**
* This is an interface for [WebRtcAudioPicker31] and [WebRtcAudioPickerLegacy] to reference methods in [WebRtcAudioOutputToggleButton] without actually depending on it.
*/
interface AudioStateUpdater {
fun updateAudioOutputState(audioOutput: WebRtcAudioOutput)
fun hidePicker()
}

View File

@@ -1,5 +1,5 @@
package org.thoughtcrime.securesms.components.webrtc;
public interface OnAudioOutputChangedListener {
void audioOutputChanged(WebRtcAudioOutput audioOutput);
void audioOutputChanged(WebRtcAudioDevice device);
}

View File

@@ -0,0 +1,82 @@
package org.thoughtcrime.securesms.components.webrtc
import kotlin.math.min
/**
* This holds UI state for [WebRtcAudioOutputToggleButton]
*/
class ToggleButtonOutputState {
private val availableOutputs: LinkedHashSet<WebRtcAudioOutput> = linkedSetOf(WebRtcAudioOutput.SPEAKER)
private var selectedDevice = 0
set(value) {
if (value >= availableOutputs.size) {
throw IndexOutOfBoundsException("Index: $value, size: ${availableOutputs.size}")
}
field = value
}
var isEarpieceAvailable: Boolean
get() = availableOutputs.contains(WebRtcAudioOutput.HANDSET)
set(value) {
if (value) {
availableOutputs.add(WebRtcAudioOutput.HANDSET)
} else {
availableOutputs.remove(WebRtcAudioOutput.HANDSET)
selectedDevice = min(selectedDevice, availableOutputs.size - 1)
}
}
var isBluetoothHeadsetAvailable: Boolean
get() = availableOutputs.contains(WebRtcAudioOutput.BLUETOOTH_HEADSET)
set(value) {
if (value) {
availableOutputs.add(WebRtcAudioOutput.BLUETOOTH_HEADSET)
} else {
availableOutputs.remove(WebRtcAudioOutput.BLUETOOTH_HEADSET)
selectedDevice = min(selectedDevice, availableOutputs.size - 1)
}
}
var isWiredHeadsetAvailable: Boolean
get() = availableOutputs.contains(WebRtcAudioOutput.WIRED_HEADSET)
set(value) {
if (value) {
availableOutputs.add(WebRtcAudioOutput.WIRED_HEADSET)
} else {
availableOutputs.remove(WebRtcAudioOutput.WIRED_HEADSET)
selectedDevice = min(selectedDevice, availableOutputs.size - 1)
}
}
@Deprecated("Used only for onSaveInstanceState.")
fun getBackingIndexForBackup(): Int {
return selectedDevice
}
@Deprecated("Used only for onRestoreInstanceState.")
fun setBackingIndexForRestore(index: Int) {
selectedDevice = 0
}
fun getCurrentOutput(): WebRtcAudioOutput {
return getOutputs()[selectedDevice]
}
fun setCurrentOutput(outputType: WebRtcAudioOutput): Boolean {
val newIndex = getOutputs().indexOf(outputType)
return if (newIndex < 0) {
false
} else {
selectedDevice = newIndex
true
}
}
fun getOutputs(): List<WebRtcAudioOutput> {
return availableOutputs.toList()
}
fun peekNext(): WebRtcAudioOutput {
val peekIndex = (selectedDevice + 1) % availableOutputs.size
return getOutputs()[peekIndex]
}
}

View File

@@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.components.webrtc
/**
* Holder class to smooth over the pre/post API 31 calls.
*
* @property webRtcAudioOutput audio device type, used by API 30 and below.
* @property deviceId specific ID for a specific device. Used only by API 31+.
*/
data class WebRtcAudioDevice(val webRtcAudioOutput: WebRtcAudioOutput, val deviceId: Int?)

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components.webrtc
import android.content.Context
import android.content.ContextWrapper
import android.content.DialogInterface
import android.media.AudioDeviceInfo
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
@@ -13,35 +12,28 @@ import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatImageView
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.webrtc.audio.AudioDeviceMapping
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
import kotlin.math.min
/**
* A UI button that triggers a picker dialog/bottom sheet allowing the user to select the audio output for the ongoing call.
*/
class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatImageView(context, attrs, defStyleAttr) {
class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatImageView(context, attrs, defStyleAttr), AudioStateUpdater {
private val TAG = Log.tag(WebRtcAudioOutputToggleButton::class.java)
private var outputState: OutputState = OutputState()
private var outputState: ToggleButtonOutputState = ToggleButtonOutputState()
private var audioOutputChangedListenerLegacy: OnAudioOutputChangedListener? = null
private var audioOutputChangedListener31: OnAudioOutputChangedListener31? = null
private var audioOutputChangedListener: OnAudioOutputChangedListener = OnAudioOutputChangedListener { Log.e(TAG, "Attempted to call audioOutputChangedListenerLegacy without initializing!") }
private var picker: DialogInterface? = null
private val clickListenerLegacy: OnClickListener = OnClickListener {
val outputs = outputState.getOutputs()
if (outputs.size >= SHOW_PICKER_THRESHOLD || !outputState.isEarpieceAvailable) {
showPickerLegacy(outputs)
picker = WebRtcAudioPickerLegacy(audioOutputChangedListener, outputState, this).showPicker(context, outputs)
} else {
setAudioOutput(outputState.peekNext(), true)
val audioOutput = outputState.peekNext()
audioOutputChangedListener.audioOutputChanged(WebRtcAudioDevice(audioOutput, null))
updateAudioOutputState(audioOutput)
}
}
@@ -49,7 +41,7 @@ class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context,
private val clickListener31 = OnClickListener {
val fragmentActivity = context.fragmentActivity()
if (fragmentActivity != null) {
showPicker31(fragmentActivity.supportFragmentManager)
picker = WebRtcAudioPicker31(audioOutputChangedListener, outputState, this).showPicker(fragmentActivity, SHOW_PICKER_THRESHOLD)
} else {
Log.e(TAG, "WebRtcAudioOutputToggleButton instantiated from a context that does not inherit from FragmentActivity.")
Toast.makeText(context, R.string.WebRtcAudioOutputToggleButton_fragment_activity_error, Toast.LENGTH_LONG).show()
@@ -83,11 +75,22 @@ class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context,
}
val currentOutput = outputState.getCurrentOutput()
val extra = when (currentOutput) {
WebRtcAudioOutput.HANDSET -> intArrayOf(R.attr.state_handset_selected)
WebRtcAudioOutput.SPEAKER -> intArrayOf(R.attr.state_speaker_selected)
WebRtcAudioOutput.BLUETOOTH_HEADSET -> intArrayOf(R.attr.state_bt_headset_selected)
WebRtcAudioOutput.WIRED_HEADSET -> intArrayOf(R.attr.state_wired_headset_selected)
val numberOfOutputs = outputState.getOutputs().size
val extra = if (numberOfOutputs < SHOW_PICKER_THRESHOLD) {
when (currentOutput) {
WebRtcAudioOutput.HANDSET -> intArrayOf(R.attr.state_speaker_off)
WebRtcAudioOutput.SPEAKER -> intArrayOf(R.attr.state_speaker_on)
WebRtcAudioOutput.BLUETOOTH_HEADSET -> intArrayOf(R.attr.state_bt_headset_selected) // should never be seen in practice.
WebRtcAudioOutput.WIRED_HEADSET -> intArrayOf(R.attr.state_wired_headset_selected) // should never be seen in practice.
}
} else {
when (currentOutput) {
WebRtcAudioOutput.HANDSET -> intArrayOf(R.attr.state_handset_selected)
WebRtcAudioOutput.SPEAKER -> intArrayOf(R.attr.state_speaker_selected)
WebRtcAudioOutput.BLUETOOTH_HEADSET -> intArrayOf(R.attr.state_bt_headset_selected)
WebRtcAudioOutput.WIRED_HEADSET -> intArrayOf(R.attr.state_wired_headset_selected)
}
}
val label = context.getString(currentOutput.labelRes)
@@ -106,89 +109,19 @@ class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context,
outputState.isEarpieceAvailable = isEarpieceAvailable
outputState.isBluetoothHeadsetAvailable = isBluetoothHeadsetAvailable
outputState.isWiredHeadsetAvailable = isHeadsetAvailable
refreshDrawableState()
}
fun setAudioOutput(audioOutput: WebRtcAudioOutput, notifyListener: Boolean) {
override fun updateAudioOutputState(audioOutput: WebRtcAudioOutput) {
val oldOutput = outputState.getCurrentOutput()
if (oldOutput != audioOutput) {
outputState.setCurrentOutput(audioOutput)
refreshDrawableState()
if (notifyListener) {
audioOutputChangedListenerLegacy?.audioOutputChanged(audioOutput)
}
}
}
fun setOnAudioOutputChangedListenerLegacy(listener: OnAudioOutputChangedListener?) {
audioOutputChangedListenerLegacy = listener
}
@RequiresApi(31)
fun setOnAudioOutputChangedListener31(listener: OnAudioOutputChangedListener31?) {
audioOutputChangedListener31 = listener
}
private fun showPickerLegacy(availableModes: List<WebRtcAudioOutput?>) {
val rv = RecyclerView(context)
val adapter = AudioOutputAdapter(
{ audioOutput: WebRtcAudioOutput ->
setAudioOutput(audioOutput, true)
hidePicker()
},
availableModes
)
adapter.setSelectedOutput(outputState.getCurrentOutput())
rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
rv.adapter = adapter
picker = MaterialAlertDialogBuilder(context)
.setTitle(R.string.WebRtcAudioOutputToggle__audio_output)
.setView(rv)
.setCancelable(true)
.show()
}
@RequiresApi(31)
private fun showPicker31(fragmentManager: FragmentManager) {
val am = ApplicationDependencies.getAndroidCallAudioManager()
if (am.availableCommunicationDevices.isEmpty()) {
Toast.makeText(context, R.string.WebRtcAudioOutputToggleButton_no_eligible_audio_i_o_detected, Toast.LENGTH_LONG).show()
return
}
val devices: List<AudioOutputOption> = am.availableCommunicationDevices.map { AudioOutputOption(it.toFriendlyName(context).toString(), AudioDeviceMapping.fromPlatformType(it.type), it.id) }
picker = WebRtcAudioOutputBottomSheet.show(fragmentManager, devices, am.communicationDevice?.id ?: -1) {
audioOutputChangedListener31?.audioOutputChanged(it.deviceId)
when (it.deviceType) {
SignalAudioManager.AudioDevice.WIRED_HEADSET -> {
outputState.isWiredHeadsetAvailable = true
setAudioOutput(WebRtcAudioOutput.WIRED_HEADSET, true)
}
SignalAudioManager.AudioDevice.EARPIECE -> {
outputState.isEarpieceAvailable = true
setAudioOutput(WebRtcAudioOutput.HANDSET, true)
}
SignalAudioManager.AudioDevice.BLUETOOTH -> {
outputState.isBluetoothHeadsetAvailable = true
setAudioOutput(WebRtcAudioOutput.BLUETOOTH_HEADSET, true)
}
SignalAudioManager.AudioDevice.SPEAKER_PHONE, SignalAudioManager.AudioDevice.NONE -> setAudioOutput(WebRtcAudioOutput.SPEAKER, true)
}
}
}
@RequiresApi(23)
private fun AudioDeviceInfo.toFriendlyName(context: Context): CharSequence {
return when (this.type) {
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> context.getString(R.string.WebRtcAudioOutputToggle__phone_earpiece)
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> context.getString(R.string.WebRtcAudioOutputToggle__speaker)
AudioDeviceInfo.TYPE_WIRED_HEADSET -> context.getString(R.string.WebRtcAudioOutputToggle__wired_headset)
AudioDeviceInfo.TYPE_USB_HEADSET -> context.getString(R.string.WebRtcAudioOutputToggle__wired_headset_usb)
else -> this.productName
}
fun setOnAudioOutputChangedListener(listener: OnAudioOutputChangedListener) {
audioOutputChangedListener = listener
}
override fun onSaveInstanceState(): Parcelable {
@@ -213,7 +146,7 @@ class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context,
}
}
private fun hidePicker() {
override fun hidePicker() {
try {
picker?.dismiss()
} catch (e: IllegalStateException) {
@@ -223,84 +156,9 @@ class WebRtcAudioOutputToggleButton @JvmOverloads constructor(context: Context,
picker = null
}
inner class OutputState {
private val availableOutputs: LinkedHashSet<WebRtcAudioOutput> = linkedSetOf(WebRtcAudioOutput.SPEAKER)
private var selectedDevice = 0
set(value) {
if (value >= availableOutputs.size) {
throw IndexOutOfBoundsException("Index: $value, size: ${availableOutputs.size}")
}
field = value
}
@Deprecated("Used only for onSaveInstanceState.")
fun getBackingIndexForBackup(): Int {
return selectedDevice
}
@Deprecated("Used only for onRestoreInstanceState.")
fun setBackingIndexForRestore(index: Int) {
selectedDevice = 0
}
fun getCurrentOutput(): WebRtcAudioOutput {
return getOutputs()[selectedDevice]
}
fun setCurrentOutput(outputType: WebRtcAudioOutput): Boolean {
val newIndex = getOutputs().indexOf(outputType)
return if (newIndex < 0) {
false
} else {
selectedDevice = newIndex
true
}
}
fun getOutputs(): List<WebRtcAudioOutput> {
return availableOutputs.toList()
}
fun peekNext(): WebRtcAudioOutput {
val peekIndex = (selectedDevice + 1) % availableOutputs.size
return getOutputs()[peekIndex]
}
var isEarpieceAvailable: Boolean
get() = availableOutputs.contains(WebRtcAudioOutput.HANDSET)
set(value) {
if (value) {
availableOutputs.add(WebRtcAudioOutput.HANDSET)
} else {
availableOutputs.remove(WebRtcAudioOutput.HANDSET)
selectedDevice = min(selectedDevice, availableOutputs.size - 1)
}
}
var isBluetoothHeadsetAvailable: Boolean
get() = availableOutputs.contains(WebRtcAudioOutput.BLUETOOTH_HEADSET)
set(value) {
if (value) {
availableOutputs.add(WebRtcAudioOutput.BLUETOOTH_HEADSET)
} else {
availableOutputs.remove(WebRtcAudioOutput.BLUETOOTH_HEADSET)
selectedDevice = min(selectedDevice, availableOutputs.size - 1)
}
}
var isWiredHeadsetAvailable: Boolean
get() = availableOutputs.contains(WebRtcAudioOutput.WIRED_HEADSET)
set(value) {
if (value) {
availableOutputs.add(WebRtcAudioOutput.WIRED_HEADSET)
} else {
availableOutputs.remove(WebRtcAudioOutput.WIRED_HEADSET)
selectedDevice = min(selectedDevice, availableOutputs.size - 1)
}
}
}
companion object {
private const val SHOW_PICKER_THRESHOLD = 3
const val SHOW_PICKER_THRESHOLD = 3
private const val STATE_OUTPUT_INDEX = "audio.output.toggle.state.output.index"
private const val STATE_HEADSET_ENABLED = "audio.output.toggle.state.headset.enabled"
private const val STATE_HANDSET_ENABLED = "audio.output.toggle.state.handset.enabled"

View File

@@ -0,0 +1,85 @@
package org.thoughtcrime.securesms.components.webrtc
import android.content.Context
import android.content.DialogInterface
import android.media.AudioDeviceInfo
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.fragment.app.FragmentActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.webrtc.audio.AudioDeviceMapping
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
/**
* This launches the bottom sheet on Android 12+ devices for selecting which audio device to use during a call.
* In cases where there are fewer than the provided threshold number of devices, it will cycle through them without presenting a bottom sheet.
*/
@RequiresApi(31)
class WebRtcAudioPicker31(private val audioOutputChangedListener: OnAudioOutputChangedListener, private val outputState: ToggleButtonOutputState, private val stateUpdater: AudioStateUpdater) {
fun showPicker(fragmentActivity: FragmentActivity, threshold: Int): DialogInterface? {
val am = ApplicationDependencies.getAndroidCallAudioManager()
if (am.availableCommunicationDevices.isEmpty()) {
Toast.makeText(fragmentActivity, R.string.WebRtcAudioOutputToggleButton_no_eligible_audio_i_o_detected, Toast.LENGTH_LONG).show()
return null
}
val devices: List<AudioOutputOption> = am.availableCommunicationDevices.map { AudioOutputOption(it.toFriendlyName(fragmentActivity).toString(), AudioDeviceMapping.fromPlatformType(it.type), it.id) }
val currentDeviceId = am.communicationDevice?.id ?: -1
if (devices.size < threshold) {
if (devices.isEmpty()) return null
val index = devices.indexOfFirst { it.deviceId == currentDeviceId }
if (index == -1) return null
onAudioDeviceSelected(devices[(index + 1) % devices.size])
return null
} else {
return WebRtcAudioOutputBottomSheet.show(fragmentActivity.supportFragmentManager, devices, currentDeviceId, onAudioDeviceSelected)
}
}
@RequiresApi(31)
val onAudioDeviceSelected: (AudioOutputOption) -> Unit = {
audioOutputChangedListener.audioOutputChanged(WebRtcAudioDevice(it.toWebRtcAudioOutput(), it.deviceId))
when (it.deviceType) {
SignalAudioManager.AudioDevice.WIRED_HEADSET -> {
outputState.isWiredHeadsetAvailable = true
stateUpdater.updateAudioOutputState(WebRtcAudioOutput.WIRED_HEADSET)
}
SignalAudioManager.AudioDevice.EARPIECE -> {
outputState.isEarpieceAvailable = true
stateUpdater.updateAudioOutputState(WebRtcAudioOutput.HANDSET)
}
SignalAudioManager.AudioDevice.BLUETOOTH -> {
outputState.isBluetoothHeadsetAvailable = true
stateUpdater.updateAudioOutputState(WebRtcAudioOutput.BLUETOOTH_HEADSET)
}
SignalAudioManager.AudioDevice.SPEAKER_PHONE, SignalAudioManager.AudioDevice.NONE -> stateUpdater.updateAudioOutputState(WebRtcAudioOutput.SPEAKER)
}
}
private fun AudioDeviceInfo.toFriendlyName(context: Context): CharSequence {
return when (this.type) {
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> context.getString(R.string.WebRtcAudioOutputToggle__phone_earpiece)
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> context.getString(R.string.WebRtcAudioOutputToggle__speaker)
AudioDeviceInfo.TYPE_WIRED_HEADSET -> context.getString(R.string.WebRtcAudioOutputToggle__wired_headset)
AudioDeviceInfo.TYPE_USB_HEADSET -> context.getString(R.string.WebRtcAudioOutputToggle__wired_headset_usb)
else -> this.productName
}
}
private fun AudioOutputOption.toWebRtcAudioOutput(): WebRtcAudioOutput {
return when (this.deviceType) {
SignalAudioManager.AudioDevice.WIRED_HEADSET -> WebRtcAudioOutput.WIRED_HEADSET
SignalAudioManager.AudioDevice.EARPIECE -> WebRtcAudioOutput.HANDSET
SignalAudioManager.AudioDevice.BLUETOOTH -> WebRtcAudioOutput.BLUETOOTH_HEADSET
SignalAudioManager.AudioDevice.SPEAKER_PHONE, SignalAudioManager.AudioDevice.NONE -> WebRtcAudioOutput.SPEAKER
}
}
}

View File

@@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.components.webrtc
import android.content.Context
import android.content.DialogInterface
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
/**
* This launches the bottom sheet on Android 11 and below devices for selecting which audio device to use during a call.
* In cases where there are only [SHOW_PICKER_THRESHOLD] devices, it will cycle through them without presenting a bottom sheet.
*/
class WebRtcAudioPickerLegacy(private val audioOutputChangedListener: OnAudioOutputChangedListener, private val outputState: ToggleButtonOutputState, private val stateUpdater: AudioStateUpdater) {
fun showPicker(context: Context, availableModes: List<WebRtcAudioOutput?>): DialogInterface? {
val rv = RecyclerView(context)
val adapter = AudioOutputAdapter(
fun(audioDevice: WebRtcAudioDevice) {
audioOutputChangedListener.audioOutputChanged(audioDevice)
stateUpdater.updateAudioOutputState(audioDevice.webRtcAudioOutput)
stateUpdater.hidePicker()
},
availableModes
)
adapter.setSelectedOutput(outputState.getCurrentOutput())
rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
rv.adapter = adapter
return MaterialAlertDialogBuilder(context)
.setTitle(R.string.WebRtcAudioOutputToggle__audio_output)
.setView(rv)
.setCancelable(true)
.show()
}
}

View File

@@ -43,6 +43,7 @@ import com.google.common.collect.Sets;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.SetUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.ResizeAnimation;
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
@@ -71,6 +72,8 @@ import java.util.Set;
public class WebRtcCallView extends ConstraintLayout {
private static final String TAG = Log.tag(WebRtcCallView.class);
private static final long TRANSITION_DURATION_MILLIS = 250;
private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8;
private static final int LARGE_ONGOING_CALL_BUTTON_MARGIN_DP = 16;
@@ -241,16 +244,21 @@ public class WebRtcCallView extends ConstraintLayout {
adjustableMarginsSet.add(videoToggle);
adjustableMarginsSet.add(audioToggle);
if (Build.VERSION.SDK_INT >= 31) {
audioToggle.setOnAudioOutputChangedListener31(deviceId -> {
runIfNonNull(controlsListener, listener -> listener.onAudioOutputChanged31(deviceId));
audioToggle.setOnAudioOutputChangedListener(webRtcAudioDevice -> {
runIfNonNull(controlsListener, listener ->
{
if (Build.VERSION.SDK_INT >= 31) {
final Integer deviceId = webRtcAudioDevice.getDeviceId();
if (deviceId != null) {
listener.onAudioOutputChanged31(deviceId);
} else {
Log.e(TAG, "Attempted to change audio output to null device ID.");
}
} else {
listener.onAudioOutputChanged(webRtcAudioDevice.getWebRtcAudioOutput());
}
});
} else {
audioToggle.setOnAudioOutputChangedListenerLegacy(outputMode -> {
runIfNonNull(controlsListener, listener -> listener.onAudioOutputChanged(outputMode));
});
}
});
videoToggle.setOnCheckedChangeListener((v, isOn) -> {
runIfNonNull(controlsListener, listener -> listener.onVideoChanged(isOn));
@@ -673,7 +681,7 @@ public class WebRtcCallView extends ConstraintLayout {
webRtcControls.isBluetoothHeadsetAvailableForAudioToggle(),
webRtcControls.isWiredHeadsetAvailableForAudioToggle());
audioToggle.setAudioOutput(webRtcControls.getAudioOutput(), false);
audioToggle.updateAudioOutputState(webRtcControls.getAudioOutput());
}
if (webRtcControls.displayCameraToggle()) {
@@ -1084,7 +1092,7 @@ public class WebRtcCallView extends ConstraintLayout {
void hideSystemUI();
void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput);
@RequiresApi(31)
void onAudioOutputChanged31(@NonNull int audioOutputAddress);
void onAudioOutputChanged31(@NonNull Integer audioOutputAddress);
void onVideoChanged(boolean isVideoEnabled);
void onMicChanged(boolean isMicEnabled);
void onCameraDirectionChanged();

View File

@@ -181,7 +181,7 @@ public final class WebRtcControls {
}
boolean isWiredHeadsetAvailableForAudioToggle() {
return availableDevices.contains(SignalAudioManager.AudioDevice.BLUETOOTH);
return availableDevices.contains(SignalAudioManager.AudioDevice.WIRED_HEADSET);
}
boolean isFadeOutEnabled() {