VoIP: continue reworking audio device management (make it works for Android < M)
This commit is contained in:
parent
d29ab94617
commit
05361c13f1
@ -44,7 +44,7 @@ import timber.log.Timber
|
|||||||
/**
|
/**
|
||||||
* Foreground service to manage calls
|
* Foreground service to manage calls
|
||||||
*/
|
*/
|
||||||
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener {
|
class CallService : VectorService() {
|
||||||
|
|
||||||
private val connections = mutableMapOf<String, CallConnection>()
|
private val connections = mutableMapOf<String, CallConnection>()
|
||||||
private val knownCalls = mutableSetOf<String>()
|
private val knownCalls = mutableSetOf<String>()
|
||||||
@ -58,9 +58,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
private var callRingPlayerIncoming: CallRingPlayerIncoming? = null
|
private var callRingPlayerIncoming: CallRingPlayerIncoming? = null
|
||||||
private var callRingPlayerOutgoing: CallRingPlayerOutgoing? = null
|
private var callRingPlayerOutgoing: CallRingPlayerOutgoing? = 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
|
||||||
@ -84,18 +81,12 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
alertManager = vectorComponent().alertManager()
|
alertManager = vectorComponent().alertManager()
|
||||||
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
|
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
|
||||||
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
|
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
|
||||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
|
|
||||||
bluetoothHeadsetStateReceiver = BluetoothHeadsetReceiver.createAndRegister(this, this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
callRingPlayerIncoming?.stop()
|
callRingPlayerIncoming?.stop()
|
||||||
callRingPlayerOutgoing?.stop()
|
callRingPlayerOutgoing?.stop()
|
||||||
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) }
|
|
||||||
wiredHeadsetStateReceiver = null
|
|
||||||
bluetoothHeadsetStateReceiver?.let { BluetoothHeadsetReceiver.unRegister(this, it) }
|
|
||||||
bluetoothHeadsetStateReceiver = null
|
|
||||||
mediaSession?.release()
|
mediaSession?.release()
|
||||||
mediaSession = null
|
mediaSession = null
|
||||||
}
|
}
|
||||||
@ -346,14 +337,4 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||||||
return this@CallService
|
return this@CallService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onHeadsetEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
|
||||||
Timber.v("## VOIP: onHeadsetEvent $event")
|
|
||||||
callManager.onWiredDeviceEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBTHeadsetEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
|
||||||
Timber.v("## VOIP: onBTHeadsetEvent $event")
|
|
||||||
callManager.onWirelessDeviceEvent(event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.
|
||||||
|
*/
|
||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package im.vector.app.features.call.audio
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.bluetooth.BluetoothProfile
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.AudioDeviceCallback
|
||||||
|
import android.media.AudioDeviceInfo
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import im.vector.app.core.services.BluetoothHeadsetReceiver
|
||||||
|
import im.vector.app.core.services.WiredHeadsetStateReceiver
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.HashSet
|
||||||
|
|
||||||
|
internal class API21AudioDeviceDetector(private val context: Context,
|
||||||
|
private val audioManager: AudioManager,
|
||||||
|
private val callAudioManager: CallAudioManager
|
||||||
|
) : CallAudioManager.AudioDeviceDetector, WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener {
|
||||||
|
|
||||||
|
private var bluetoothAdapter: BluetoothAdapter? = null
|
||||||
|
private var connectedBlueToothHeadset: BluetoothProfile? = null
|
||||||
|
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
|
||||||
|
private var bluetoothHeadsetStateReceiver: BluetoothHeadsetReceiver? = null
|
||||||
|
|
||||||
|
private val onAudioDeviceChangeRunner = Runnable {
|
||||||
|
val devices = getAvailableSoundDevices()
|
||||||
|
callAudioManager.replaceDevices(devices)
|
||||||
|
Timber.i(" Available audio devices: $devices")
|
||||||
|
callAudioManager.updateAudioRoute()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAvailableSoundDevices(): Set<CallAudioManager.Device> {
|
||||||
|
return HashSet<CallAudioManager.Device>().apply {
|
||||||
|
if (isBluetoothHeadsetOn()) add(CallAudioManager.Device.WIRELESS_HEADSET)
|
||||||
|
if(isWiredHeadsetOn()){
|
||||||
|
add(CallAudioManager.Device.HEADSET)
|
||||||
|
}else {
|
||||||
|
add(CallAudioManager.Device.PHONE)
|
||||||
|
}
|
||||||
|
add(CallAudioManager.Device.SPEAKER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun isWiredHeadsetOn(): Boolean {
|
||||||
|
return audioManager.isWiredHeadsetOn
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBluetoothHeadsetOn(): Boolean {
|
||||||
|
Timber.v("## VOIP: AudioManager isBluetoothHeadsetOn")
|
||||||
|
try {
|
||||||
|
if (connectedBlueToothHeadset == null) return false.also {
|
||||||
|
Timber.v("## VOIP: AudioManager no connected bluetooth headset")
|
||||||
|
}
|
||||||
|
if (!audioManager.isBluetoothScoAvailableOffCall) return false.also {
|
||||||
|
Timber.v("## VOIP: AudioManager isBluetoothScoAvailableOffCall false")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e("## VOIP: AudioManager isBluetoothHeadsetOn failure ${failure.localizedMessage}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to trigger an audio route update when devices change. It
|
||||||
|
* makes sure the operation is performed on the audio thread.
|
||||||
|
*/
|
||||||
|
private fun onAudioDeviceChange() {
|
||||||
|
callAudioManager.runInAudioThread(onAudioDeviceChangeRunner)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
Timber.i("Start using $this as the audio device handler")
|
||||||
|
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(context, this)
|
||||||
|
bluetoothHeadsetStateReceiver = BluetoothHeadsetReceiver.createAndRegister(context, this)
|
||||||
|
val bm: BluetoothManager? = context.getSystemService()
|
||||||
|
val adapter = bm?.adapter
|
||||||
|
Timber.d("## VOIP Bluetooth adapter $adapter")
|
||||||
|
bluetoothAdapter = adapter
|
||||||
|
adapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||||
|
override fun onServiceDisconnected(profile: Int) {
|
||||||
|
Timber.d("## VOIP onServiceDisconnected $profile")
|
||||||
|
if (profile == BluetoothProfile.HEADSET) {
|
||||||
|
connectedBlueToothHeadset = null
|
||||||
|
onAudioDeviceChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
|
||||||
|
Timber.d("## VOIP onServiceConnected $profile , proxy:$proxy")
|
||||||
|
if (profile == BluetoothProfile.HEADSET) {
|
||||||
|
connectedBlueToothHeadset = proxy
|
||||||
|
onAudioDeviceChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, BluetoothProfile.HEADSET)
|
||||||
|
onAudioDeviceChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
Timber.i("Stop using $this as the audio device handler")
|
||||||
|
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(context, it) }
|
||||||
|
wiredHeadsetStateReceiver = null
|
||||||
|
bluetoothHeadsetStateReceiver?.let { BluetoothHeadsetReceiver.unRegister(context, it) }
|
||||||
|
bluetoothHeadsetStateReceiver = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHeadsetEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||||
|
Timber.v("onHeadsetEvent $event")
|
||||||
|
onAudioDeviceChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBTHeadsetEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||||
|
Timber.v("onBTHeadsetEvent $event")
|
||||||
|
onAudioDeviceChange()
|
||||||
|
}
|
||||||
|
}
|
@ -13,16 +13,11 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package im.vector.app.features.call.audio
|
package im.vector.app.features.call.audio
|
||||||
|
|
||||||
import android.media.AudioAttributes
|
|
||||||
import android.media.AudioDeviceCallback
|
import android.media.AudioDeviceCallback
|
||||||
import android.media.AudioDeviceInfo
|
import android.media.AudioDeviceInfo
|
||||||
import android.media.AudioFocusRequest
|
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioManager.OnAudioFocusChangeListener
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -31,7 +26,7 @@ import java.util.HashSet
|
|||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
|
internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
|
||||||
private val callAudioManager: CallAudioManager
|
private val callAudioManager: CallAudioManager
|
||||||
) : CallAudioManager.AudioDeviceDetector{
|
) : CallAudioManager.AudioDeviceDetector {
|
||||||
|
|
||||||
private val onAudioDeviceChangeRunner = Runnable {
|
private val onAudioDeviceChangeRunner = Runnable {
|
||||||
val devices: MutableSet<CallAudioManager.Device> = HashSet()
|
val devices: MutableSet<CallAudioManager.Device> = HashSet()
|
||||||
|
@ -23,7 +23,7 @@ import timber.log.Timber
|
|||||||
import java.util.HashSet
|
import java.util.HashSet
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class CallAudioManager(context: Context, val configChange: (() -> Unit)?) {
|
class CallAudioManager(private val context: Context, val configChange: (() -> Unit)?) {
|
||||||
|
|
||||||
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
private var audioDeviceDetector: AudioDeviceDetector? = null
|
private var audioDeviceDetector: AudioDeviceDetector? = null
|
||||||
@ -57,11 +57,13 @@ class CallAudioManager(context: Context, val configChange: (() -> Unit)?) {
|
|||||||
|
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
audioDeviceDetector?.stop()
|
audioDeviceDetector?.stop()
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
audioDeviceDetector = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
audioDeviceDetector = API23AudioDeviceDetector(audioManager, this)
|
API23AudioDeviceDetector(audioManager, this)
|
||||||
|
} else {
|
||||||
|
API21AudioDeviceDetector(context, audioManager, this)
|
||||||
}
|
}
|
||||||
audioDeviceRouter = DefaultAudioDeviceRouter(audioManager, this)
|
|
||||||
audioDeviceDetector?.start()
|
audioDeviceDetector?.start()
|
||||||
|
audioDeviceRouter = DefaultAudioDeviceRouter(audioManager, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runInAudioThread(runnable: Runnable) {
|
fun runInAudioThread(runnable: Runnable) {
|
||||||
@ -190,8 +192,9 @@ class CallAudioManager(context: Context, val configChange: (() -> Unit)?) {
|
|||||||
*
|
*
|
||||||
* @param devices The new devices list.
|
* @param devices The new devices list.
|
||||||
*/
|
*/
|
||||||
fun replaceDevices(devices: MutableSet<Device>) {
|
fun replaceDevices(devices: Set<Device>) {
|
||||||
_availableDevices = devices
|
_availableDevices.clear()
|
||||||
|
_availableDevices.addAll(devices)
|
||||||
resetSelectedDevice()
|
resetSelectedDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user