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
|
||||
*/
|
||||
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener {
|
||||
class CallService : VectorService() {
|
||||
|
||||
private val connections = mutableMapOf<String, CallConnection>()
|
||||
private val knownCalls = mutableSetOf<String>()
|
||||
|
@ -58,9 +58,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||
private var callRingPlayerIncoming: CallRingPlayerIncoming? = 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,
|
||||
// such as those found on wired and wireless headsets, into the appropriate callbacks in your app
|
||||
private var mediaSession: MediaSessionCompat? = null
|
||||
|
@ -84,18 +81,12 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||
alertManager = vectorComponent().alertManager()
|
||||
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
|
||||
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
|
||||
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)
|
||||
bluetoothHeadsetStateReceiver = BluetoothHeadsetReceiver.createAndRegister(this, this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
callRingPlayerIncoming?.stop()
|
||||
callRingPlayerOutgoing?.stop()
|
||||
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) }
|
||||
wiredHeadsetStateReceiver = null
|
||||
bluetoothHeadsetStateReceiver?.let { BluetoothHeadsetReceiver.unRegister(this, it) }
|
||||
bluetoothHeadsetStateReceiver = null
|
||||
mediaSession?.release()
|
||||
mediaSession = null
|
||||
}
|
||||
|
@ -346,14 +337,4 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
|||
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
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package im.vector.app.features.call.audio
|
||||
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioDeviceCallback
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioFocusRequest
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.OnAudioFocusChangeListener
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import timber.log.Timber
|
||||
|
@ -31,7 +26,7 @@ import java.util.HashSet
|
|||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
|
||||
private val callAudioManager: CallAudioManager
|
||||
) : CallAudioManager.AudioDeviceDetector{
|
||||
) : CallAudioManager.AudioDeviceDetector {
|
||||
|
||||
private val onAudioDeviceChangeRunner = Runnable {
|
||||
val devices: MutableSet<CallAudioManager.Device> = HashSet()
|
||||
|
|
|
@ -23,7 +23,7 @@ import timber.log.Timber
|
|||
import java.util.HashSet
|
||||
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 var audioDeviceDetector: AudioDeviceDetector? = null
|
||||
|
@ -57,11 +57,13 @@ class CallAudioManager(context: Context, val configChange: (() -> Unit)?) {
|
|||
|
||||
private fun setup() {
|
||||
audioDeviceDetector?.stop()
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
audioDeviceDetector = API23AudioDeviceDetector(audioManager, this)
|
||||
audioDeviceDetector = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
API23AudioDeviceDetector(audioManager, this)
|
||||
} else {
|
||||
API21AudioDeviceDetector(context, audioManager, this)
|
||||
}
|
||||
audioDeviceRouter = DefaultAudioDeviceRouter(audioManager, this)
|
||||
audioDeviceDetector?.start()
|
||||
audioDeviceRouter = DefaultAudioDeviceRouter(audioManager, this)
|
||||
}
|
||||
|
||||
fun runInAudioThread(runnable: Runnable) {
|
||||
|
@ -190,8 +192,9 @@ class CallAudioManager(context: Context, val configChange: (() -> Unit)?) {
|
|||
*
|
||||
* @param devices The new devices list.
|
||||
*/
|
||||
fun replaceDevices(devices: MutableSet<Device>) {
|
||||
_availableDevices = devices
|
||||
fun replaceDevices(devices: Set<Device>) {
|
||||
_availableDevices.clear()
|
||||
_availableDevices.addAll(devices)
|
||||
resetSelectedDevice()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue