Another bluetooth proto

This commit is contained in:
Sylvain Berfini 2025-09-22 14:03:23 +02:00
parent ae7a3c5bce
commit d4baa8e588
3 changed files with 79 additions and 13 deletions

View file

@ -26,13 +26,11 @@ import androidx.core.telecom.CallControlResult
import androidx.core.telecom.CallControlScope import androidx.core.telecom.CallControlScope
import androidx.core.telecom.CallEndpointCompat import androidx.core.telecom.CallEndpointCompat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.core.AudioDevice
import org.linphone.core.Call import org.linphone.core.Call
import org.linphone.core.CallListenerStub import org.linphone.core.CallListenerStub
import org.linphone.core.Reason import org.linphone.core.Reason
@ -143,14 +141,14 @@ class TelecomCallControlCallback(
}.launchIn(scope) }.launchIn(scope)
callControl.currentCallEndpoint.onEach { endpoint -> callControl.currentCallEndpoint.onEach { endpoint ->
var newEndpointToUse = endpoint // var newEndpointToUse = endpoint
if (endpointUpdateRequestFromLinphone) { if (endpointUpdateRequestFromLinphone) {
Log.i("$TAG Linphone requests to use [${endpoint.name}] audio endpoint with type [${endpointTypeToString(endpoint.type)}]") Log.i("$TAG Linphone requests to use [${endpoint.name}] audio endpoint with type [${endpointTypeToString(endpoint.type)}]")
} else { } else {
Log.i("$TAG Android requests us to use [${endpoint.name}] audio endpoint with type [${endpointTypeToString(endpoint.type)}]") Log.i("$TAG Android requests us to use [${endpoint.name}] audio endpoint with type [${endpointTypeToString(endpoint.type)}]")
} }
val requestedEndpoint = latestLinphoneRequestedEndpoint /*val requestedEndpoint = latestLinphoneRequestedEndpoint
if (endpointUpdateRequestFromLinphone && requestedEndpoint != null && requestedEndpoint != endpoint) { if (endpointUpdateRequestFromLinphone && requestedEndpoint != null && requestedEndpoint != endpoint) {
Log.w("$TAG WARNING: Linphone requested endpoint [${requestedEndpoint.name}] but Telecom Manager notified endpoint [${endpoint.name}], trying to use the one we requested anyway") Log.w("$TAG WARNING: Linphone requested endpoint [${requestedEndpoint.name}] but Telecom Manager notified endpoint [${endpoint.name}], trying to use the one we requested anyway")
newEndpointToUse = requestedEndpoint newEndpointToUse = requestedEndpoint
@ -163,7 +161,13 @@ class TelecomCallControlCallback(
Log.w("$TAG Device isn't connected to Android Auto, do not follow system request to change audio endpoint to [${newEndpointToUse.name}] with type [${endpointTypeToString(type)}]") Log.w("$TAG Device isn't connected to Android Auto, do not follow system request to change audio endpoint to [${newEndpointToUse.name}] with type [${endpointTypeToString(type)}]")
return@onEach return@onEach
} }
endpointUpdateRequestFromLinphone = false endpointUpdateRequestFromLinphone = false*/
/*val type = endpoint.type
if (!endpointUpdateRequestFromLinphone && !coreContext.isConnectedToAndroidAuto && (type == CallEndpointCompat.Companion.TYPE_EARPIECE || type == CallEndpointCompat.Companion.TYPE_SPEAKER)) {
endpointUpdateRequestFromLinphone = false
Log.w("$TAG Device isn't connected to Android Auto, do not follow system request to change audio endpoint to [${endpoint.name}] with type [${endpointTypeToString(type)}]")
return@onEach
}
// Change audio route in SDK, this way the usual listener will trigger // Change audio route in SDK, this way the usual listener will trigger
// and we'll be able to update the UI accordingly // and we'll be able to update the UI accordingly
@ -186,6 +190,7 @@ class TelecomCallControlCallback(
} }
if (route.isNotEmpty()) { if (route.isNotEmpty()) {
coreContext.postOnCoreThread { coreContext.postOnCoreThread {
Log.i("$TAG Requesting Linphone to change audio route to [$route]([${endpointTypeToString(endpoint.type)}])")
if (!AudioUtils.applyAudioRouteChangeInLinphone(call, route)) { if (!AudioUtils.applyAudioRouteChangeInLinphone(call, route)) {
Log.w("$TAG Failed to apply audio route change, trying again in 200ms") Log.w("$TAG Failed to apply audio route change, trying again in 200ms")
coreContext.postOnCoreThreadDelayed({ coreContext.postOnCoreThreadDelayed({
@ -193,7 +198,7 @@ class TelecomCallControlCallback(
}, 200) }, 200)
} }
} }
} }*/
}.launchIn(scope) }.launchIn(scope)
callControl.isMuted.onEach { muted -> callControl.isMuted.onEach { muted ->
@ -224,7 +229,7 @@ class TelecomCallControlCallback(
}.launchIn(scope) }.launchIn(scope)
} }
fun applyAudioRouteToCallWithId(routes: List<AudioDevice.Type>): Boolean { /*fun applyAudioRouteToCallWithId(routes: List<AudioDevice.Type>): Boolean {
Log.i("$TAG Looking for audio endpoint with type [${routes.first()}]") Log.i("$TAG Looking for audio endpoint with type [${routes.first()}]")
var wiredHeadsetFound = false var wiredHeadsetFound = false
@ -260,13 +265,14 @@ class TelecomCallControlCallback(
continue continue
} }
var endpointChangeSuccessful = false
scope.launch { scope.launch {
Log.i("$TAG Requesting audio endpoint change to [${endpoint.name}] with type [${endpointTypeToString(endpoint.type)}]") Log.i("$TAG Requesting audio endpoint change to [${endpoint.name}] with type [${endpointTypeToString(endpoint.type)}]")
endpointUpdateRequestFromLinphone = true endpointUpdateRequestFromLinphone = true
latestLinphoneRequestedEndpoint = endpoint latestLinphoneRequestedEndpoint = endpoint
var result: CallControlResult = callControl.requestEndpointChange(endpoint) var result: CallControlResult = callControl.requestEndpointChange(endpoint)
var attempts = 1 var attempts = 1
while (result is CallControlResult.Error && attempts <= 10) { while (result is CallControlResult.Error && attempts <= 2) {
delay(100) delay(100)
Log.i( Log.i(
"$TAG Previous attempt failed [$result], requesting again audio endpoint change to [${endpoint.name}] with type [${endpointTypeToString(endpoint.type)}]" "$TAG Previous attempt failed [$result], requesting again audio endpoint change to [${endpoint.name}] with type [${endpointTypeToString(endpoint.type)}]"
@ -282,10 +288,11 @@ class TelecomCallControlCallback(
"$TAG It took [$attempts] attempt(s) to change endpoint audio device..." "$TAG It took [$attempts] attempt(s) to change endpoint audio device..."
) )
currentEndpoint = endpoint.type currentEndpoint = endpoint.type
endpointChangeSuccessful = true
} }
} }
return true return endpointChangeSuccessful
} }
} }
@ -297,7 +304,7 @@ class TelecomCallControlCallback(
Log.e("$TAG No matching endpoint found") Log.e("$TAG No matching endpoint found")
} }
return false return false
} }*/
private fun answerCall() { private fun answerCall() {
val isVideo = LinphoneUtils.isVideoEnabled(call) val isVideo = LinphoneUtils.isVideoEnabled(call)

View file

@ -20,6 +20,8 @@
package org.linphone.telecom package org.linphone.telecom
import android.content.Context import android.content.Context
import android.media.AudioDeviceInfo
import android.media.AudioManager
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.telecom.CallAttributesCompat import androidx.core.telecom.CallAttributesCompat
import androidx.core.telecom.CallException import androidx.core.telecom.CallException
@ -29,13 +31,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.AudioDevice
import org.linphone.core.Call import org.linphone.core.Call
import org.linphone.core.Core import org.linphone.core.Core
import org.linphone.core.CoreListenerStub import org.linphone.core.CoreListenerStub
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.utils.LinphoneUtils import org.linphone.utils.LinphoneUtils
import androidx.core.net.toUri import androidx.core.net.toUri
import org.linphone.core.AudioDevice
import org.linphone.utils.AudioUtils
class TelecomManager class TelecomManager
@WorkerThread @WorkerThread
@ -66,6 +69,9 @@ class TelecomManager
@WorkerThread @WorkerThread
override fun onLastCallEnded(core: Core) { override fun onLastCallEnded(core: Core) {
currentlyFollowedCalls = 0 currentlyFollowedCalls = 0
val audioManager = coreContext.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
Log.i("$TAG Clearing communication device")
audioManager.clearCommunicationDevice()
} }
} }
@ -87,6 +93,18 @@ class TelecomManager
} catch (e: Exception) { } catch (e: Exception) {
Log.e("$TAG Can't init TelecomManager: $e") Log.e("$TAG Can't init TelecomManager: $e")
} }
val listener =
AudioManager.OnCommunicationDeviceChangedListener { device -> // Handle changes
Log.i("$TAG Communication device changed: $device")
if (device?.type != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
AudioUtils.applyAudioRouteChangeInLinphone(null, listOf(AudioDevice.Type.Speaker), true)
} else {
AudioUtils.applyAudioRouteChangeInLinphone(null, listOf(AudioDevice.Type.Bluetooth), true)
}
}
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.addOnCommunicationDeviceChangedListener(context.mainExecutor, listener)
} }
@WorkerThread @WorkerThread
@ -214,12 +232,33 @@ class TelecomManager
Log.i( Log.i(
"$TAG Looking for audio endpoint with type [${routes.first()}] for call with ID [$callId]" "$TAG Looking for audio endpoint with type [${routes.first()}] for call with ID [$callId]"
) )
val callControlCallback = map[callId] /*val callControlCallback = map[callId]
if (callControlCallback == null) { if (callControlCallback == null) {
Log.w("$TAG Failed to find callbacks for call with ID [$callId]") Log.w("$TAG Failed to find callbacks for call with ID [$callId]")
return false return false
} }
return callControlCallback.applyAudioRouteToCallWithId(routes) return callControlCallback.applyAudioRouteToCallWithId(routes)*/
val audioManager = coreContext.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (AudioDevice.Type.Bluetooth in routes) {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
return true
}
}
} else {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
return true
}
}
}
return false
} }
} }

View file

@ -95,6 +95,26 @@ class AudioUtils {
null null
} }
/*val audioManager = coreContext.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (AudioDevice.Type.Bluetooth in types) {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
}
}
} else {
val devices = audioManager.availableCommunicationDevices
for (device in devices) {
if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && device.isSink) {
Log.i("$TAG [Telecom] Setting communication device: $device")
audioManager.setCommunicationDevice(device)
}
}
}*/
// applyAudioRouteChangeInLinphone(currentCall, types, output)
if (!skipTelecom) { if (!skipTelecom) {
val callId = currentCall?.callLog?.callId.orEmpty() val callId = currentCall?.callLog?.callId.orEmpty()
Log.i("$TAG Trying to change audio endpoint using Telecom Manager APIs") Log.i("$TAG Trying to change audio endpoint using Telecom Manager APIs")