Reworked audio route changes to make them go through Telecom Manager API if enabled to make smartwatches act as bluetooth headset properly
This commit is contained in:
parent
6d6ea9b4c4
commit
f66c90d356
6 changed files with 99 additions and 42 deletions
|
@ -34,6 +34,7 @@ import android.view.WindowManager
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.telecom.NativeCallWrapper
|
||||
|
||||
@TargetApi(26)
|
||||
class Api26Compatibility {
|
||||
|
@ -133,5 +134,9 @@ class Api26Compatibility {
|
|||
.build()
|
||||
vibrator.vibrate(effect, audioAttrs)
|
||||
}
|
||||
|
||||
fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int) {
|
||||
connection.setAudioRoute(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.Content
|
||||
import org.linphone.mediastream.Version
|
||||
import org.linphone.telecom.NativeCallWrapper
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class Compatibility {
|
||||
|
@ -169,6 +170,12 @@ class Compatibility {
|
|||
}
|
||||
}
|
||||
|
||||
fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int) {
|
||||
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
|
||||
Api26Compatibility.changeAudioRouteForTelecomManager(connection, route)
|
||||
}
|
||||
}
|
||||
|
||||
/* Contacts */
|
||||
|
||||
fun createShortcutsToContacts(context: Context) {
|
||||
|
|
|
@ -72,10 +72,10 @@ class NativeCallWrapper(var callId: String) : Connection() {
|
|||
if (call != null) {
|
||||
call.microphoneMuted = state.isMuted
|
||||
when (state.route) {
|
||||
CallAudioState.ROUTE_EARPIECE -> AudioRouteUtils.routeAudioToEarpiece(call)
|
||||
CallAudioState.ROUTE_SPEAKER -> AudioRouteUtils.routeAudioToSpeaker(call)
|
||||
CallAudioState.ROUTE_BLUETOOTH -> AudioRouteUtils.routeAudioToBluetooth(call)
|
||||
CallAudioState.ROUTE_WIRED_HEADSET -> AudioRouteUtils.routeAudioToHeadset(call)
|
||||
CallAudioState.ROUTE_EARPIECE -> AudioRouteUtils.routeAudioToEarpiece(call, true)
|
||||
CallAudioState.ROUTE_SPEAKER -> AudioRouteUtils.routeAudioToSpeaker(call, true)
|
||||
CallAudioState.ROUTE_BLUETOOTH -> AudioRouteUtils.routeAudioToBluetooth(call, true)
|
||||
CallAudioState.ROUTE_WIRED_HEADSET -> AudioRouteUtils.routeAudioToHeadset(call, true)
|
||||
}
|
||||
} else {
|
||||
selfDestroy()
|
||||
|
|
|
@ -30,8 +30,6 @@ import org.linphone.core.CoreListenerStub
|
|||
import org.linphone.core.tools.Log
|
||||
|
||||
class TelecomConnectionService : ConnectionService() {
|
||||
private val connections = arrayListOf<NativeCallWrapper>()
|
||||
|
||||
private val listener: CoreListenerStub = object : CoreListenerStub() {
|
||||
override fun onCallStateChanged(
|
||||
core: Core,
|
||||
|
@ -42,7 +40,7 @@ class TelecomConnectionService : ConnectionService() {
|
|||
Log.i("[Telecom Connection Service] call [${call.callLog.callId}] state changed: $state")
|
||||
when (call.state) {
|
||||
Call.State.OutgoingProgress -> {
|
||||
for (connection in connections) {
|
||||
for (connection in TelecomHelper.get().connections) {
|
||||
if (connection.callId.isEmpty()) {
|
||||
connection.callId = core.currentCall?.callLog?.callId ?: ""
|
||||
}
|
||||
|
@ -105,7 +103,7 @@ class TelecomConnectionService : ConnectionService() {
|
|||
connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED)
|
||||
Log.i("[Telecom Connection Service] Address is $providedHandle")
|
||||
|
||||
connections.add(connection)
|
||||
TelecomHelper.get().connections.add(connection)
|
||||
connection
|
||||
} else {
|
||||
Log.e("[Telecom Connection Service] Error: $accountHandle $componentName")
|
||||
|
@ -150,7 +148,7 @@ class TelecomConnectionService : ConnectionService() {
|
|||
connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED)
|
||||
Log.i("[Telecom Connection Service] Address is $providedHandle")
|
||||
|
||||
connections.add(connection)
|
||||
TelecomHelper.get().connections.add(connection)
|
||||
connection
|
||||
} else {
|
||||
Log.e("[Telecom Connection Service] Error: $accountHandle $componentName")
|
||||
|
@ -163,26 +161,20 @@ class TelecomConnectionService : ConnectionService() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getConnectionForCallId(callId: String): NativeCallWrapper? {
|
||||
return connections.find { connection ->
|
||||
connection.callId == callId
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallError(call: Call) {
|
||||
val connection = getConnectionForCallId(call.callLog.callId)
|
||||
val connection = TelecomHelper.get().findConnectionForCallId(call.callLog.callId)
|
||||
connection ?: return
|
||||
|
||||
connections.remove(connection)
|
||||
TelecomHelper.get().connections.remove(connection)
|
||||
connection.setDisconnected(DisconnectCause(DisconnectCause.ERROR))
|
||||
connection.destroy()
|
||||
}
|
||||
|
||||
private fun onCallEnded(call: Call) {
|
||||
val connection = getConnectionForCallId(call.callLog.callId)
|
||||
val connection = TelecomHelper.get().findConnectionForCallId(call.callLog.callId)
|
||||
connection ?: return
|
||||
|
||||
connections.remove(connection)
|
||||
TelecomHelper.get().connections.remove(connection)
|
||||
val reason = call.reason
|
||||
Log.i("[Telecom Connection Service] Call ended with reason: $reason")
|
||||
connection.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
|
||||
|
@ -190,7 +182,7 @@ class TelecomConnectionService : ConnectionService() {
|
|||
}
|
||||
|
||||
private fun onCallConnected(call: Call) {
|
||||
val connection = getConnectionForCallId(call.callLog.callId)
|
||||
val connection = TelecomHelper.get().findConnectionForCallId(call.callLog.callId)
|
||||
connection ?: return
|
||||
|
||||
if (connection.state != Connection.STATE_HOLDING) {
|
||||
|
|
|
@ -49,6 +49,8 @@ class TelecomHelper private constructor(context: Context) {
|
|||
|
||||
private var account: PhoneAccount = initPhoneAccount(context)
|
||||
|
||||
val connections = arrayListOf<NativeCallWrapper>()
|
||||
|
||||
private val listener: CoreListenerStub = object : CoreListenerStub() {
|
||||
override fun onCallStateChanged(
|
||||
core: Core,
|
||||
|
@ -137,6 +139,12 @@ class TelecomHelper private constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun findConnectionForCallId(callId: String): NativeCallWrapper? {
|
||||
return connections.find { connection ->
|
||||
connection.callId == callId
|
||||
}
|
||||
}
|
||||
|
||||
private fun initPhoneAccount(context: Context): PhoneAccount {
|
||||
val account: PhoneAccount? = findExistingAccount(context)
|
||||
if (account == null) {
|
||||
|
|
|
@ -19,16 +19,19 @@
|
|||
*/
|
||||
package org.linphone.utils
|
||||
|
||||
import android.telecom.CallAudioState
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.compatibility.Compatibility
|
||||
import org.linphone.core.AudioDevice
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.telecom.TelecomHelper
|
||||
|
||||
class AudioRouteUtils {
|
||||
companion object {
|
||||
private fun routeAudioTo(
|
||||
private fun applyAudioRouteChange(
|
||||
call: Call?,
|
||||
types: List<AudioDevice.Type>,
|
||||
call: Call? = null,
|
||||
output: Boolean = true
|
||||
) {
|
||||
val listSize = types.size
|
||||
|
@ -71,30 +74,72 @@ class AudioRouteUtils {
|
|||
Log.e("[Audio Route Helper] Couldn't find [$typesNames] audio device")
|
||||
}
|
||||
|
||||
fun routeAudioToEarpiece(call: Call? = null) {
|
||||
routeAudioTo(arrayListOf(AudioDevice.Type.Earpiece), call)
|
||||
}
|
||||
|
||||
fun routeAudioToSpeaker(call: Call? = null) {
|
||||
routeAudioTo(arrayListOf(AudioDevice.Type.Speaker), call)
|
||||
}
|
||||
|
||||
fun routeAudioToBluetooth(call: Call? = null) {
|
||||
routeAudioTo(arrayListOf(AudioDevice.Type.Bluetooth), call)
|
||||
if (isBluetoothAudioRecorderAvailable()) {
|
||||
Log.i("[Audio Route Helper] Bluetooth device is able to record audio, also change input audio device")
|
||||
routeAudioTo(arrayListOf(AudioDevice.Type.Bluetooth), call, false)
|
||||
private fun changeCaptureDeviceToMatchAudioRoute(call: Call?, types: List<AudioDevice.Type>) {
|
||||
when (types.first()) {
|
||||
AudioDevice.Type.Bluetooth -> {
|
||||
if (isBluetoothAudioRecorderAvailable()) {
|
||||
Log.i("[Audio Route Helper] Bluetooth device is able to record audio, also change input audio device")
|
||||
applyAudioRouteChange(call, arrayListOf(AudioDevice.Type.Bluetooth), false)
|
||||
}
|
||||
}
|
||||
AudioDevice.Type.Headset, AudioDevice.Type.Headphones -> {
|
||||
if (isHeadsetAudioRecorderAvailable()) {
|
||||
Log.i("[Audio Route Helper] Headphones/headset device is able to record audio, also change input audio device")
|
||||
applyAudioRouteChange(call, (arrayListOf(AudioDevice.Type.Headphones, AudioDevice.Type.Headset)), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun routeAudioToHeadset(call: Call? = null) {
|
||||
routeAudioTo(arrayListOf(AudioDevice.Type.Headphones, AudioDevice.Type.Headset), call)
|
||||
if (isHeadsetAudioRecorderAvailable()) {
|
||||
Log.i("[Audio Route Helper] Headphones/headset device is able to record audio, also change input audio device")
|
||||
routeAudioTo((arrayListOf(AudioDevice.Type.Headphones, AudioDevice.Type.Headset)), call, false)
|
||||
private fun routeAudioTo(
|
||||
call: Call?,
|
||||
types: List<AudioDevice.Type>,
|
||||
skipTelecom: Boolean = false
|
||||
) {
|
||||
val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls[0]
|
||||
if ((call != null || currentCall != null) && !skipTelecom && TelecomHelper.exists()) {
|
||||
val callToUse = call ?: currentCall
|
||||
Log.i("[Audio Route Helper] Call provided & Telecom Helper exists, trying to dispatch audio route change through Telecom API")
|
||||
val connection = TelecomHelper.get().findConnectionForCallId(callToUse.callLog.callId)
|
||||
if (connection != null) {
|
||||
val route = when (types.first()) {
|
||||
AudioDevice.Type.Earpiece -> CallAudioState.ROUTE_EARPIECE
|
||||
AudioDevice.Type.Speaker -> CallAudioState.ROUTE_SPEAKER
|
||||
AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> CallAudioState.ROUTE_WIRED_HEADSET
|
||||
AudioDevice.Type.Bluetooth, AudioDevice.Type.BluetoothA2DP -> CallAudioState.ROUTE_BLUETOOTH
|
||||
else -> CallAudioState.ROUTE_WIRED_OR_EARPIECE
|
||||
}
|
||||
Log.i("[Audio Route Helper] Telecom Helper & matching connection found, dispatching audio route change through it")
|
||||
// We will be called here again by NativeCallWrapper.onCallAudioStateChanged()
|
||||
// but this time with skipTelecom = true
|
||||
Compatibility.changeAudioRouteForTelecomManager(connection, route)
|
||||
} else {
|
||||
Log.w("[Audio Route Helper] Telecom Helper found but no matching connection!")
|
||||
applyAudioRouteChange(callToUse, types)
|
||||
changeCaptureDeviceToMatchAudioRoute(callToUse, types)
|
||||
}
|
||||
} else {
|
||||
applyAudioRouteChange(call, types)
|
||||
changeCaptureDeviceToMatchAudioRoute(call, types)
|
||||
}
|
||||
}
|
||||
|
||||
fun routeAudioToEarpiece(call: Call? = null, skipTelecom: Boolean = false) {
|
||||
routeAudioTo(call, arrayListOf(AudioDevice.Type.Earpiece), skipTelecom)
|
||||
}
|
||||
|
||||
fun routeAudioToSpeaker(call: Call? = null, skipTelecom: Boolean = false) {
|
||||
routeAudioTo(call, arrayListOf(AudioDevice.Type.Speaker), skipTelecom)
|
||||
}
|
||||
|
||||
fun routeAudioToBluetooth(call: Call? = null, skipTelecom: Boolean = false) {
|
||||
routeAudioTo(call, arrayListOf(AudioDevice.Type.Bluetooth), skipTelecom)
|
||||
}
|
||||
|
||||
fun routeAudioToHeadset(call: Call? = null, skipTelecom: Boolean = false) {
|
||||
routeAudioTo(call, arrayListOf(AudioDevice.Type.Headphones, AudioDevice.Type.Headset), skipTelecom)
|
||||
}
|
||||
|
||||
fun isSpeakerAudioRouteCurrentlyUsed(call: Call? = null): Boolean {
|
||||
if (coreContext.core.callsNb == 0) {
|
||||
Log.w("[Audio Route Helper] No call found, so speaker audio route isn't used")
|
||||
|
@ -104,7 +149,7 @@ class AudioRouteUtils {
|
|||
val conference = coreContext.core.conference
|
||||
|
||||
val audioDevice = if (conference != null && conference.isIn) conference.outputAudioDevice else currentCall.outputAudioDevice
|
||||
Log.i("[Audio Route Helper] Audio device currently in use is [${audioDevice?.deviceName}] with type (${audioDevice?.type})")
|
||||
Log.i("[Audio Route Helper] Playback audio device currently in use is [${audioDevice?.deviceName}] with type (${audioDevice?.type})")
|
||||
return audioDevice?.type == AudioDevice.Type.Speaker
|
||||
}
|
||||
|
||||
|
@ -117,7 +162,7 @@ class AudioRouteUtils {
|
|||
val conference = coreContext.core.conference
|
||||
|
||||
val audioDevice = if (conference != null && conference.isIn) conference.outputAudioDevice else currentCall.outputAudioDevice
|
||||
Log.i("[Audio Route Helper] Audio device currently in use is [${audioDevice?.deviceName}] with type (${audioDevice?.type})")
|
||||
Log.i("[Audio Route Helper] Playback audio device currently in use is [${audioDevice?.deviceName}] with type (${audioDevice?.type})")
|
||||
return audioDevice?.type == AudioDevice.Type.Bluetooth
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue