mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Bluetooth headset support
This commit is contained in:
parent
4c61dfef62
commit
5dfa08ace6
@ -3,6 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="im.vector.riotx">
|
||||
|
||||
<!-- Needed for VOIP call to detect and switch to headset-->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.services
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothClass
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class BluetoothHeadsetReceiver : BroadcastReceiver() {
|
||||
|
||||
interface EventListener {
|
||||
fun onBTHeadsetEvent(event: BTHeadsetPlugEvent)
|
||||
}
|
||||
|
||||
var delegate: WeakReference<EventListener>? = null
|
||||
|
||||
data class BTHeadsetPlugEvent(
|
||||
val plugged: Boolean,
|
||||
val headsetName: String?,
|
||||
/**
|
||||
* BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE
|
||||
* BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO
|
||||
* AUDIO_VIDEO_WEARABLE_HEADSET
|
||||
*/
|
||||
val deviceClass: Int
|
||||
)
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
// This intent will have 3 extras:
|
||||
// EXTRA_CONNECTION_STATE - The current connection state
|
||||
// EXTRA_PREVIOUS_CONNECTION_STATE}- The previous connection state.
|
||||
// BluetoothDevice#EXTRA_DEVICE - The remote device.
|
||||
// EXTRA_CONNECTION_STATE or EXTRA_PREVIOUS_CONNECTION_STATE can be any of
|
||||
// STATE_DISCONNECTED}, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING
|
||||
|
||||
val headsetConnected = when (intent?.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)) {
|
||||
BluetoothAdapter.STATE_CONNECTED -> true
|
||||
BluetoothAdapter.STATE_DISCONNECTED -> false
|
||||
else -> return // ignore intermediate states
|
||||
}
|
||||
|
||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
val deviceName = device?.name
|
||||
when (device?.bluetoothClass?.deviceClass) {
|
||||
BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE,
|
||||
BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO,
|
||||
BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET -> {
|
||||
// filter only device that we care about for
|
||||
delegate?.get()?.onBTHeadsetEvent(
|
||||
BTHeadsetPlugEvent(
|
||||
plugged = headsetConnected,
|
||||
headsetName = deviceName,
|
||||
deviceClass = device.bluetoothClass.deviceClass
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createAndRegister(context: Context, listener: EventListener): BluetoothHeadsetReceiver {
|
||||
val receiver = BluetoothHeadsetReceiver()
|
||||
receiver.delegate = WeakReference(listener)
|
||||
context.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED))
|
||||
return receiver
|
||||
}
|
||||
|
||||
fun unRegister(context: Context, receiver: BluetoothHeadsetReceiver) {
|
||||
context.unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ import timber.log.Timber
|
||||
/**
|
||||
* Foreground service to manage calls
|
||||
*/
|
||||
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener {
|
||||
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener {
|
||||
|
||||
private val connections = mutableMapOf<String, CallConnection>()
|
||||
|
||||
@ -43,10 +43,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
private var callRingPlayer: CallRingPlayer? = null
|
||||
|
||||
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
|
||||
private var bluetoothHeadsetStateReceiver: BluetoothHeadsetReceiver? = null
|
||||
|
||||
// A media button receiver receives and helps translate hardware media playback buttons,
|
||||
// such as those found on wired and wireless headsets, into the appropriate callbacks in your app
|
||||
private var mediaSession : MediaSessionCompat? = null
|
||||
private var mediaSession: MediaSessionCompat? = null
|
||||
private val mediaSessionButtonCallback = object : MediaSessionCompat.Callback() {
|
||||
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
|
||||
val keyEvent = mediaButtonEvent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false
|
||||
@ -64,6 +65,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
webRtcPeerConnectionManager = vectorComponent().webRtcPeerConnectionManager()
|
||||
callRingPlayer = CallRingPlayer(applicationContext)
|
||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
|
||||
bluetoothHeadsetStateReceiver = BluetoothHeadsetReceiver.createAndRegister(this, this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@ -71,6 +73,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
callRingPlayer?.stop()
|
||||
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) }
|
||||
wiredHeadsetStateReceiver = null
|
||||
bluetoothHeadsetStateReceiver?.let { BluetoothHeadsetReceiver.unRegister(this, it) }
|
||||
bluetoothHeadsetStateReceiver = null
|
||||
mediaSession?.release()
|
||||
mediaSession = null
|
||||
}
|
||||
@ -365,6 +369,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
|
||||
override fun onHeadsetEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
Timber.v("## VOIP: onHeadsetEvent $event")
|
||||
webRtcPeerConnectionManager.onWireDeviceEvent(event)
|
||||
webRtcPeerConnectionManager.onWiredDeviceEvent(event)
|
||||
}
|
||||
|
||||
override fun onBTHeadsetEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||
Timber.v("## VOIP: onBTHeadsetEvent $event")
|
||||
webRtcPeerConnectionManager.onWirelessDeviceEvent(event)
|
||||
}
|
||||
}
|
||||
|
@ -16,28 +16,59 @@
|
||||
|
||||
package im.vector.riotx.features.call
|
||||
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.AudioManager
|
||||
import im.vector.matrix.android.api.session.call.MxCall
|
||||
import im.vector.riotx.core.services.WiredHeadsetStateReceiver
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class CallAudioManager(
|
||||
val applicationContext: Context
|
||||
val applicationContext: Context,
|
||||
val configChange: (() -> Unit)?
|
||||
) {
|
||||
|
||||
enum class SoundDevice {
|
||||
PHONE,
|
||||
SPEAKER,
|
||||
HEADSET
|
||||
HEADSET,
|
||||
WIRELESS_HEADSET
|
||||
}
|
||||
|
||||
private val audioManager: AudioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
/*
|
||||
* if all calls to audio manager not in the same thread it's not working well...
|
||||
*/
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
private var audioManager: AudioManager? = null
|
||||
|
||||
private var savedIsSpeakerPhoneOn = false
|
||||
private var savedIsMicrophoneMute = false
|
||||
private var savedAudioMode = AudioManager.MODE_INVALID
|
||||
|
||||
private var connectedBlueToothHeadset: BluetoothProfile? = null
|
||||
private var wantsBluetoothConnection = false
|
||||
|
||||
init {
|
||||
executor.execute {
|
||||
audioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
}
|
||||
val bm = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
|
||||
bm?.adapter?.getProfileProxy(applicationContext, object : BluetoothProfile.ServiceListener {
|
||||
override fun onServiceDisconnected(profile: Int) {
|
||||
connectedBlueToothHeadset = null
|
||||
}
|
||||
|
||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
|
||||
connectedBlueToothHeadset = proxy
|
||||
configChange?.invoke()
|
||||
}
|
||||
}, BluetoothProfile.HEADSET)
|
||||
}
|
||||
|
||||
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
||||
|
||||
// Called on the listener to notify if the audio focus for this listener has been changed.
|
||||
@ -49,6 +80,7 @@ class CallAudioManager(
|
||||
|
||||
fun startForCall(mxCall: MxCall) {
|
||||
Timber.v("## VOIP: AudioManager startForCall ${mxCall.callId}")
|
||||
val audioManager = audioManager ?: return
|
||||
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn
|
||||
savedIsMicrophoneMute = audioManager.isMicrophoneMute
|
||||
savedAudioMode = audioManager.mode
|
||||
@ -72,77 +104,150 @@ class CallAudioManager(
|
||||
// Always disable microphone mute during a WebRTC call.
|
||||
setMicrophoneMute(false)
|
||||
|
||||
// If there are no headset, start video output in speaker
|
||||
// (you can't watch the video and have the phone close to your ear)
|
||||
if (mxCall.isVideoCall && !isHeadsetOn()) {
|
||||
setSpeakerphoneOn(true)
|
||||
} else {
|
||||
// if a headset is plugged, sound will be directed to it
|
||||
// (can't really force earpiece when headset is plugged)
|
||||
setSpeakerphoneOn(false)
|
||||
executor.execute {
|
||||
// If there are no headset, start video output in speaker
|
||||
// (you can't watch the video and have the phone close to your ear)
|
||||
if (mxCall.isVideoCall && !isHeadsetOn()) {
|
||||
Timber.v("##VOIP: AudioManager default to speaker ")
|
||||
setCurrentSoundDevice(SoundDevice.SPEAKER)
|
||||
} else {
|
||||
// if a wired headset is plugged, sound will be directed to it
|
||||
// (can't really force earpiece when headset is plugged)
|
||||
if (isBluetoothHeadsetOn()) {
|
||||
Timber.v("##VOIP: AudioManager default to WIRELESS_HEADSET ")
|
||||
setCurrentSoundDevice(SoundDevice.WIRELESS_HEADSET)
|
||||
// try now in case already connected?
|
||||
audioManager.isBluetoothScoOn = true
|
||||
} else {
|
||||
Timber.v("##VOIP: AudioManager default to PHONE/HEADSET ")
|
||||
setCurrentSoundDevice(if (isWiredHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAvailableSoundDevices(): List<SoundDevice> {
|
||||
return listOf(
|
||||
if (isHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE,
|
||||
SoundDevice.SPEAKER
|
||||
)
|
||||
return ArrayList<SoundDevice>().apply {
|
||||
if (isBluetoothHeadsetOn()) add(SoundDevice.WIRELESS_HEADSET)
|
||||
add(if (isWiredHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE)
|
||||
add(SoundDevice.SPEAKER)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
Timber.v("## VOIP: AudioManager stopCall")
|
||||
executor.execute {
|
||||
// Restore previously stored audio states.
|
||||
setSpeakerphoneOn(savedIsSpeakerPhoneOn)
|
||||
setMicrophoneMute(savedIsMicrophoneMute)
|
||||
audioManager?.mode = savedAudioMode
|
||||
|
||||
// Restore previously stored audio states.
|
||||
setSpeakerphoneOn(savedIsSpeakerPhoneOn)
|
||||
setMicrophoneMute(savedIsMicrophoneMute)
|
||||
audioManager.mode = savedAudioMode
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
audioManager.abandonAudioFocus(audioFocusChangeListener)
|
||||
@Suppress("DEPRECATION")
|
||||
audioManager?.abandonAudioFocus(audioFocusChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentSoundDevice(): SoundDevice {
|
||||
val audioManager = audioManager ?: return SoundDevice.PHONE
|
||||
if (audioManager.isSpeakerphoneOn) {
|
||||
return SoundDevice.SPEAKER
|
||||
} else {
|
||||
if (isBluetoothHeadsetOn() && (wantsBluetoothConnection || audioManager.isBluetoothScoOn)) return SoundDevice.WIRELESS_HEADSET
|
||||
return if (isHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentSoundDevice(device: SoundDevice) {
|
||||
when (device) {
|
||||
SoundDevice.HEADSET,
|
||||
SoundDevice.PHONE -> setSpeakerphoneOn(false)
|
||||
SoundDevice.SPEAKER -> setSpeakerphoneOn(true)
|
||||
executor.execute {
|
||||
Timber.v("## VOIP setCurrentSoundDevice $device")
|
||||
when (device) {
|
||||
SoundDevice.HEADSET,
|
||||
SoundDevice.PHONE -> {
|
||||
wantsBluetoothConnection = false
|
||||
if (isBluetoothHeadsetOn()) {
|
||||
audioManager?.stopBluetoothSco()
|
||||
audioManager?.isBluetoothScoOn = false
|
||||
}
|
||||
setSpeakerphoneOn(false)
|
||||
}
|
||||
SoundDevice.SPEAKER -> {
|
||||
setSpeakerphoneOn(true)
|
||||
wantsBluetoothConnection = false
|
||||
audioManager?.stopBluetoothSco()
|
||||
audioManager?.isBluetoothScoOn = false
|
||||
}
|
||||
SoundDevice.WIRELESS_HEADSET -> {
|
||||
setSpeakerphoneOn(false)
|
||||
// I cannot directly do it, i have to start then wait that it's connected
|
||||
// to route to bt
|
||||
audioManager?.startBluetoothSco()
|
||||
wantsBluetoothConnection = true
|
||||
}
|
||||
}
|
||||
|
||||
configChange?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun bluetoothStateChange(plugged: Boolean) {
|
||||
executor.execute {
|
||||
if (plugged && wantsBluetoothConnection) {
|
||||
audioManager?.isBluetoothScoOn = true
|
||||
} else if (!plugged && !wantsBluetoothConnection) {
|
||||
audioManager?.stopBluetoothSco()
|
||||
}
|
||||
|
||||
configChange?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun wiredStateChange(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
executor.execute {
|
||||
// if it's plugged and speaker is on we should route to headset
|
||||
if (event.plugged && getCurrentSoundDevice() == SoundDevice.SPEAKER) {
|
||||
setCurrentSoundDevice(CallAudioManager.SoundDevice.HEADSET)
|
||||
} else if (!event.plugged) {
|
||||
// if it's unplugged ? always route to speaker?
|
||||
// this is questionable?
|
||||
if (!wantsBluetoothConnection) {
|
||||
setCurrentSoundDevice(SoundDevice.SPEAKER)
|
||||
}
|
||||
}
|
||||
configChange?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isHeadsetOn(): Boolean {
|
||||
return isWiredHeadsetOn() || isBluetoothHeadsetOn()
|
||||
}
|
||||
|
||||
private fun isWiredHeadsetOn(): Boolean {
|
||||
@Suppress("DEPRECATION")
|
||||
return audioManager.isWiredHeadsetOn || audioManager.isBluetoothScoOn
|
||||
return audioManager?.isWiredHeadsetOn ?: false
|
||||
}
|
||||
|
||||
private fun isBluetoothHeadsetOn(): Boolean {
|
||||
return connectedBlueToothHeadset != null
|
||||
}
|
||||
|
||||
/** Sets the speaker phone mode. */
|
||||
private fun setSpeakerphoneOn(on: Boolean) {
|
||||
Timber.v("## VOIP: AudioManager setSpeakerphoneOn $on")
|
||||
val wasOn = audioManager.isSpeakerphoneOn
|
||||
val wasOn = audioManager?.isSpeakerphoneOn ?: false
|
||||
if (wasOn == on) {
|
||||
return
|
||||
}
|
||||
audioManager.isSpeakerphoneOn = on
|
||||
audioManager?.isSpeakerphoneOn = on
|
||||
}
|
||||
|
||||
/** Sets the microphone mute state. */
|
||||
private fun setMicrophoneMute(on: Boolean) {
|
||||
Timber.v("## VOIP: AudioManager setMicrophoneMute $on")
|
||||
val wasMuted = audioManager.isMicrophoneMute
|
||||
val wasMuted = audioManager?.isMicrophoneMute ?: false
|
||||
if (wasMuted == on) {
|
||||
return
|
||||
}
|
||||
audioManager.isMicrophoneMute = on
|
||||
|
||||
audioManager.isMusicActive
|
||||
audioManager?.isMicrophoneMute = on
|
||||
}
|
||||
|
||||
/** true if the device has a telephony radio with data
|
||||
|
@ -55,6 +55,10 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
private fun showSoundDeviceChooser(available: List<CallAudioManager.SoundDevice>, current: CallAudioManager.SoundDevice) {
|
||||
val soundDevices = available.map {
|
||||
when (it) {
|
||||
CallAudioManager.SoundDevice.WIRELESS_HEADSET -> span {
|
||||
text = getString(R.string.sound_device_wireless_headset)
|
||||
textStyle = if (current == it) "bold" else "normal"
|
||||
}
|
||||
CallAudioManager.SoundDevice.PHONE -> span {
|
||||
text = getString(R.string.sound_device_phone)
|
||||
textStyle = if (current == it) "bold" else "normal"
|
||||
@ -82,6 +86,9 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
getString(R.string.sound_device_headset) -> {
|
||||
callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.HEADSET))
|
||||
}
|
||||
getString(R.string.sound_device_wireless_headset) -> {
|
||||
callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.WIRELESS_HEADSET))
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
@ -104,6 +111,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
|
||||
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
|
||||
CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset)
|
||||
CallAudioManager.SoundDevice.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.model.call.CallCandidatesConten
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallHangupContent
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.services.BluetoothHeadsetReceiver
|
||||
import im.vector.riotx.core.services.CallService
|
||||
import im.vector.riotx.core.services.WiredHeadsetStateReceiver
|
||||
import io.reactivex.disposables.Disposable
|
||||
@ -88,7 +89,11 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
currentCallsListeners.remove(listener)
|
||||
}
|
||||
|
||||
val audioManager = CallAudioManager(context.applicationContext)
|
||||
val audioManager = CallAudioManager(context.applicationContext) {
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onAudioDevicesChange(this) }
|
||||
}
|
||||
}
|
||||
|
||||
data class CallContext(
|
||||
val mxCall: MxCall,
|
||||
@ -672,19 +677,16 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
||||
close()
|
||||
}
|
||||
|
||||
fun onWireDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWiredDeviceEvent $event")
|
||||
currentCall ?: return
|
||||
// if it's plugged and speaker is on we should route to headset
|
||||
if (event.plugged && audioManager.getCurrentSoundDevice() == CallAudioManager.SoundDevice.SPEAKER) {
|
||||
audioManager.setCurrentSoundDevice(CallAudioManager.SoundDevice.HEADSET)
|
||||
} else if (!event.plugged) {
|
||||
// if it's unplugged ? always route to speaker?
|
||||
// this is questionable?
|
||||
audioManager.setCurrentSoundDevice(CallAudioManager.SoundDevice.SPEAKER)
|
||||
}
|
||||
currentCallsListeners.forEach {
|
||||
it.onAudioDevicesChange(this)
|
||||
}
|
||||
// sometimes we received un-wanted unplugged...
|
||||
audioManager.wiredStateChange(event)
|
||||
}
|
||||
|
||||
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWirelessDeviceEvent $event")
|
||||
audioManager.bluetoothStateChange(event.plugged)
|
||||
}
|
||||
|
||||
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
|
||||
|
@ -218,6 +218,7 @@
|
||||
<string name="sound_device_phone">Phone</string>
|
||||
<string name="sound_device_speaker">Speaker</string>
|
||||
<string name="sound_device_headset">Headset</string>
|
||||
<string name="sound_device_wireless_headset">Wireless Headset</string>
|
||||
|
||||
|
||||
<string name="option_send_files">Send files</string>
|
||||
|
Loading…
Reference in New Issue
Block a user