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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="im.vector.riotx">
|
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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<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
|
* Foreground service to manage calls
|
||||||
*/
|
*/
|
||||||
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener {
|
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener {
|
||||||
|
|
||||||
private val connections = mutableMapOf<String, CallConnection>()
|
private val connections = mutableMapOf<String, CallConnection>()
|
||||||
|
|
||||||
@ -43,10 +43,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
private var callRingPlayer: CallRingPlayer? = null
|
private var callRingPlayer: CallRingPlayer? = null
|
||||||
|
|
||||||
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
|
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
|
||||||
|
private var bluetoothHeadsetStateReceiver: BluetoothHeadsetReceiver? = null
|
||||||
|
|
||||||
// A media button receiver receives and helps translate hardware media playback buttons,
|
// 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
|
// 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() {
|
private val mediaSessionButtonCallback = object : MediaSessionCompat.Callback() {
|
||||||
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
|
||||||
val keyEvent = mediaButtonEvent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false
|
val keyEvent = mediaButtonEvent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false
|
||||||
@ -64,6 +65,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
webRtcPeerConnectionManager = vectorComponent().webRtcPeerConnectionManager()
|
webRtcPeerConnectionManager = vectorComponent().webRtcPeerConnectionManager()
|
||||||
callRingPlayer = CallRingPlayer(applicationContext)
|
callRingPlayer = CallRingPlayer(applicationContext)
|
||||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
|
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
|
||||||
|
bluetoothHeadsetStateReceiver = BluetoothHeadsetReceiver.createAndRegister(this, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@ -71,6 +73,8 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
callRingPlayer?.stop()
|
callRingPlayer?.stop()
|
||||||
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) }
|
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) }
|
||||||
wiredHeadsetStateReceiver = null
|
wiredHeadsetStateReceiver = null
|
||||||
|
bluetoothHeadsetStateReceiver?.let { BluetoothHeadsetReceiver.unRegister(this, it) }
|
||||||
|
bluetoothHeadsetStateReceiver = null
|
||||||
mediaSession?.release()
|
mediaSession?.release()
|
||||||
mediaSession = null
|
mediaSession = null
|
||||||
}
|
}
|
||||||
@ -365,6 +369,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
|
|
||||||
override fun onHeadsetEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
override fun onHeadsetEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||||
Timber.v("## VOIP: onHeadsetEvent $event")
|
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
|
package im.vector.riotx.features.call
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.bluetooth.BluetoothProfile
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import im.vector.matrix.android.api.session.call.MxCall
|
import im.vector.matrix.android.api.session.call.MxCall
|
||||||
|
import im.vector.riotx.core.services.WiredHeadsetStateReceiver
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class CallAudioManager(
|
class CallAudioManager(
|
||||||
val applicationContext: Context
|
val applicationContext: Context,
|
||||||
|
val configChange: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
enum class SoundDevice {
|
enum class SoundDevice {
|
||||||
PHONE,
|
PHONE,
|
||||||
SPEAKER,
|
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 savedIsSpeakerPhoneOn = false
|
||||||
private var savedIsMicrophoneMute = false
|
private var savedIsMicrophoneMute = false
|
||||||
private var savedAudioMode = AudioManager.MODE_INVALID
|
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 ->
|
private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
|
||||||
|
|
||||||
// Called on the listener to notify if the audio focus for this listener has been changed.
|
// 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) {
|
fun startForCall(mxCall: MxCall) {
|
||||||
Timber.v("## VOIP: AudioManager startForCall ${mxCall.callId}")
|
Timber.v("## VOIP: AudioManager startForCall ${mxCall.callId}")
|
||||||
|
val audioManager = audioManager ?: return
|
||||||
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn
|
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn
|
||||||
savedIsMicrophoneMute = audioManager.isMicrophoneMute
|
savedIsMicrophoneMute = audioManager.isMicrophoneMute
|
||||||
savedAudioMode = audioManager.mode
|
savedAudioMode = audioManager.mode
|
||||||
@ -72,77 +104,150 @@ class CallAudioManager(
|
|||||||
// Always disable microphone mute during a WebRTC call.
|
// Always disable microphone mute during a WebRTC call.
|
||||||
setMicrophoneMute(false)
|
setMicrophoneMute(false)
|
||||||
|
|
||||||
// If there are no headset, start video output in speaker
|
executor.execute {
|
||||||
// (you can't watch the video and have the phone close to your ear)
|
// If there are no headset, start video output in speaker
|
||||||
if (mxCall.isVideoCall && !isHeadsetOn()) {
|
// (you can't watch the video and have the phone close to your ear)
|
||||||
setSpeakerphoneOn(true)
|
if (mxCall.isVideoCall && !isHeadsetOn()) {
|
||||||
} else {
|
Timber.v("##VOIP: AudioManager default to speaker ")
|
||||||
// if a headset is plugged, sound will be directed to it
|
setCurrentSoundDevice(SoundDevice.SPEAKER)
|
||||||
// (can't really force earpiece when headset is plugged)
|
} else {
|
||||||
setSpeakerphoneOn(false)
|
// 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> {
|
fun getAvailableSoundDevices(): List<SoundDevice> {
|
||||||
return listOf(
|
return ArrayList<SoundDevice>().apply {
|
||||||
if (isHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE,
|
if (isBluetoothHeadsetOn()) add(SoundDevice.WIRELESS_HEADSET)
|
||||||
SoundDevice.SPEAKER
|
add(if (isWiredHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE)
|
||||||
)
|
add(SoundDevice.SPEAKER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
Timber.v("## VOIP: AudioManager stopCall")
|
Timber.v("## VOIP: AudioManager stopCall")
|
||||||
|
executor.execute {
|
||||||
|
// Restore previously stored audio states.
|
||||||
|
setSpeakerphoneOn(savedIsSpeakerPhoneOn)
|
||||||
|
setMicrophoneMute(savedIsMicrophoneMute)
|
||||||
|
audioManager?.mode = savedAudioMode
|
||||||
|
|
||||||
// Restore previously stored audio states.
|
@Suppress("DEPRECATION")
|
||||||
setSpeakerphoneOn(savedIsSpeakerPhoneOn)
|
audioManager?.abandonAudioFocus(audioFocusChangeListener)
|
||||||
setMicrophoneMute(savedIsMicrophoneMute)
|
}
|
||||||
audioManager.mode = savedAudioMode
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
audioManager.abandonAudioFocus(audioFocusChangeListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentSoundDevice(): SoundDevice {
|
fun getCurrentSoundDevice(): SoundDevice {
|
||||||
|
val audioManager = audioManager ?: return SoundDevice.PHONE
|
||||||
if (audioManager.isSpeakerphoneOn) {
|
if (audioManager.isSpeakerphoneOn) {
|
||||||
return SoundDevice.SPEAKER
|
return SoundDevice.SPEAKER
|
||||||
} else {
|
} else {
|
||||||
|
if (isBluetoothHeadsetOn() && (wantsBluetoothConnection || audioManager.isBluetoothScoOn)) return SoundDevice.WIRELESS_HEADSET
|
||||||
return if (isHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE
|
return if (isHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCurrentSoundDevice(device: SoundDevice) {
|
fun setCurrentSoundDevice(device: SoundDevice) {
|
||||||
when (device) {
|
executor.execute {
|
||||||
SoundDevice.HEADSET,
|
Timber.v("## VOIP setCurrentSoundDevice $device")
|
||||||
SoundDevice.PHONE -> setSpeakerphoneOn(false)
|
when (device) {
|
||||||
SoundDevice.SPEAKER -> setSpeakerphoneOn(true)
|
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 {
|
private fun isHeadsetOn(): Boolean {
|
||||||
|
return isWiredHeadsetOn() || isBluetoothHeadsetOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isWiredHeadsetOn(): Boolean {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
return audioManager.isWiredHeadsetOn || audioManager.isBluetoothScoOn
|
return audioManager?.isWiredHeadsetOn ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBluetoothHeadsetOn(): Boolean {
|
||||||
|
return connectedBlueToothHeadset != null
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the speaker phone mode. */
|
/** Sets the speaker phone mode. */
|
||||||
private fun setSpeakerphoneOn(on: Boolean) {
|
private fun setSpeakerphoneOn(on: Boolean) {
|
||||||
Timber.v("## VOIP: AudioManager setSpeakerphoneOn $on")
|
Timber.v("## VOIP: AudioManager setSpeakerphoneOn $on")
|
||||||
val wasOn = audioManager.isSpeakerphoneOn
|
val wasOn = audioManager?.isSpeakerphoneOn ?: false
|
||||||
if (wasOn == on) {
|
if (wasOn == on) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
audioManager.isSpeakerphoneOn = on
|
audioManager?.isSpeakerphoneOn = on
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the microphone mute state. */
|
/** Sets the microphone mute state. */
|
||||||
private fun setMicrophoneMute(on: Boolean) {
|
private fun setMicrophoneMute(on: Boolean) {
|
||||||
Timber.v("## VOIP: AudioManager setMicrophoneMute $on")
|
Timber.v("## VOIP: AudioManager setMicrophoneMute $on")
|
||||||
val wasMuted = audioManager.isMicrophoneMute
|
val wasMuted = audioManager?.isMicrophoneMute ?: false
|
||||||
if (wasMuted == on) {
|
if (wasMuted == on) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
audioManager.isMicrophoneMute = on
|
audioManager?.isMicrophoneMute = on
|
||||||
|
|
||||||
audioManager.isMusicActive
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** true if the device has a telephony radio with data
|
/** 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) {
|
private fun showSoundDeviceChooser(available: List<CallAudioManager.SoundDevice>, current: CallAudioManager.SoundDevice) {
|
||||||
val soundDevices = available.map {
|
val soundDevices = available.map {
|
||||||
when (it) {
|
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 {
|
CallAudioManager.SoundDevice.PHONE -> span {
|
||||||
text = getString(R.string.sound_device_phone)
|
text = getString(R.string.sound_device_phone)
|
||||||
textStyle = if (current == it) "bold" else "normal"
|
textStyle = if (current == it) "bold" else "normal"
|
||||||
@ -82,6 +86,9 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
getString(R.string.sound_device_headset) -> {
|
getString(R.string.sound_device_headset) -> {
|
||||||
callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.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)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
@ -104,6 +111,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||||||
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
|
CallAudioManager.SoundDevice.PHONE -> getString(R.string.sound_device_phone)
|
||||||
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
|
CallAudioManager.SoundDevice.SPEAKER -> getString(R.string.sound_device_speaker)
|
||||||
CallAudioManager.SoundDevice.HEADSET -> getString(R.string.sound_device_headset)
|
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.CallHangupContent
|
||||||
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
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.CallService
|
||||||
import im.vector.riotx.core.services.WiredHeadsetStateReceiver
|
import im.vector.riotx.core.services.WiredHeadsetStateReceiver
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
@ -88,7 +89,11 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
currentCallsListeners.remove(listener)
|
currentCallsListeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioManager = CallAudioManager(context.applicationContext)
|
val audioManager = CallAudioManager(context.applicationContext) {
|
||||||
|
currentCallsListeners.forEach {
|
||||||
|
tryThis { it.onAudioDevicesChange(this) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class CallContext(
|
data class CallContext(
|
||||||
val mxCall: MxCall,
|
val mxCall: MxCall,
|
||||||
@ -672,19 +677,16 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onWireDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||||
|
Timber.v("## VOIP onWiredDeviceEvent $event")
|
||||||
currentCall ?: return
|
currentCall ?: return
|
||||||
// if it's plugged and speaker is on we should route to headset
|
// sometimes we received un-wanted unplugged...
|
||||||
if (event.plugged && audioManager.getCurrentSoundDevice() == CallAudioManager.SoundDevice.SPEAKER) {
|
audioManager.wiredStateChange(event)
|
||||||
audioManager.setCurrentSoundDevice(CallAudioManager.SoundDevice.HEADSET)
|
}
|
||||||
} else if (!event.plugged) {
|
|
||||||
// if it's unplugged ? always route to speaker?
|
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||||
// this is questionable?
|
Timber.v("## VOIP onWirelessDeviceEvent $event")
|
||||||
audioManager.setCurrentSoundDevice(CallAudioManager.SoundDevice.SPEAKER)
|
audioManager.bluetoothStateChange(event.plugged)
|
||||||
}
|
|
||||||
currentCallsListeners.forEach {
|
|
||||||
it.onAudioDevicesChange(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
|
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
|
||||||
|
@ -218,6 +218,7 @@
|
|||||||
<string name="sound_device_phone">Phone</string>
|
<string name="sound_device_phone">Phone</string>
|
||||||
<string name="sound_device_speaker">Speaker</string>
|
<string name="sound_device_speaker">Speaker</string>
|
||||||
<string name="sound_device_headset">Headset</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>
|
<string name="option_send_files">Send files</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user