|
|
|
|
@ -34,11 +34,18 @@ class UtunConnection(
|
|
|
|
|
/** Доступные CallEndpoint'ы (для requestCallEndpointChange), обновляются системой. */ |
|
|
|
|
private val availableEndpoints = CopyOnWriteArrayList<CallEndpoint>() |
|
|
|
|
var onRouteChanged: ((AudioRoute) -> Unit)? = null |
|
|
|
|
var onEndpointsChanged: (() -> Unit)? = null |
|
|
|
|
val headsetAvailable: Boolean? get() = if (Build.VERSION.SDK_INT >= 34) { |
|
|
|
|
if (availableEndpoints.isEmpty()) null else availableEndpoints.any { |
|
|
|
|
it.endpointType == CallEndpoint.TYPE_BLUETOOTH || it.endpointType == CallEndpoint.TYPE_WIRED_HEADSET |
|
|
|
|
} |
|
|
|
|
} else callAudioState?.let { it.supportedRouteMask and (CallAudioState.ROUTE_BLUETOOTH or CallAudioState.ROUTE_WIRED_HEADSET) != 0 } |
|
|
|
|
var confirmedRoute: AudioRoute? = null |
|
|
|
|
private set |
|
|
|
|
private var wantedRoute: AudioRoute? = null |
|
|
|
|
private var requestedEndpoint: android.os.ParcelUuid? = null |
|
|
|
|
private var requestSerial = 0L |
|
|
|
|
private var requestedLegacyRoute = 0 |
|
|
|
|
|
|
|
|
|
init { |
|
|
|
|
setConnectionProperties(PROPERTY_SELF_MANAGED) |
|
|
|
|
@ -75,6 +82,8 @@ class UtunConnection(
|
|
|
|
|
} |
|
|
|
|
if (route != null) confirmRoute(route) |
|
|
|
|
else LogManager.addLog("WARN", "UtunConn", "unrecognized audio route=${audioState.route}") |
|
|
|
|
onEndpointsChanged?.invoke() |
|
|
|
|
wantedRoute?.let { routeTo(it) } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -92,7 +101,7 @@ class UtunConnection(
|
|
|
|
|
private fun confirmRoute(route: AudioRoute, endpoint: android.os.ParcelUuid? = null) { |
|
|
|
|
confirmedRoute = route |
|
|
|
|
if (wantedRoute == route && (requestedEndpoint == null || requestedEndpoint == endpoint)) { |
|
|
|
|
wantedRoute = null; requestedEndpoint = null |
|
|
|
|
wantedRoute = null; requestedLegacyRoute = 0 |
|
|
|
|
} |
|
|
|
|
LogManager.addLog("INFO", "UtunConn", "route confirmed id=$callId route=$route pending=$wantedRoute") |
|
|
|
|
onRouteChanged?.invoke(route) |
|
|
|
|
@ -103,6 +112,7 @@ class UtunConnection(
|
|
|
|
|
this.availableEndpoints.addAll(availableEndpoints) |
|
|
|
|
LogManager.addLog("DEBUG", "UtunConn", |
|
|
|
|
"availableEndpoints -> %s".format(availableEndpoints.joinToString { "${it.endpointName}/${it.endpointType}" })) |
|
|
|
|
onEndpointsChanged?.invoke() |
|
|
|
|
wantedRoute?.let { routeTo(it) } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -114,6 +124,8 @@ class UtunConnection(
|
|
|
|
|
|
|
|
|
|
/** Завершение с причиной (коды приложения: 0 — отбой, 2 — отклонено, 7 — ошибка). */ |
|
|
|
|
fun markDisconnected(reason: Int) { |
|
|
|
|
wantedRoute = null; requestedEndpoint = null; requestedLegacyRoute = 0; ++requestSerial |
|
|
|
|
onRouteChanged = null; onEndpointsChanged = null |
|
|
|
|
val code = when (reason) { |
|
|
|
|
2 -> DisconnectCause.REJECTED |
|
|
|
|
7 -> DisconnectCause.ERROR |
|
|
|
|
@ -126,8 +138,9 @@ class UtunConnection(
|
|
|
|
|
/** Маршрутизация аудио через Telecom (CallEndpoint API на Android 14+, иначе setAudioRoute). */ |
|
|
|
|
fun routeTo(route: AudioRoute) { |
|
|
|
|
wantedRoute = route |
|
|
|
|
if (confirmedRoute == route && requestedEndpoint == null) { wantedRoute = null; return } |
|
|
|
|
if (confirmedRoute == route && requestedEndpoint == null && requestedLegacyRoute == 0) { wantedRoute = null; return } |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { |
|
|
|
|
if (requestedEndpoint != null) return // До ответа сохраняем только последнее желание пользователя. |
|
|
|
|
val ep = when (route) { |
|
|
|
|
AudioRoute.EARPIECE -> availableEndpoints.firstOrNull { it.endpointType == CallEndpoint.TYPE_EARPIECE } |
|
|
|
|
AudioRoute.SPEAKER -> availableEndpoints.firstOrNull { it.endpointType == CallEndpoint.TYPE_SPEAKER } |
|
|
|
|
@ -143,10 +156,19 @@ class UtunConnection(
|
|
|
|
|
requestCallEndpointChange(ep, executor, object : OutcomeReceiver<Void, CallEndpointException> { |
|
|
|
|
override fun onResult(result: Void?) { |
|
|
|
|
LogManager.addLog("INFO", "UtunConn", "endpoint change ok -> %d".format(ep.endpointType)) |
|
|
|
|
if (requestSerial == serial) { |
|
|
|
|
requestedEndpoint = null |
|
|
|
|
wantedRoute?.takeIf { it != route }?.let { routeTo(it) } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
override fun onError(error: CallEndpointException) { |
|
|
|
|
if (requestSerial == serial) { requestedEndpoint = null; wantedRoute = null } |
|
|
|
|
LogManager.addLog("WARN", "UtunConn", "endpoint change failed: %s".format(error.message)) |
|
|
|
|
if (requestSerial == serial) { |
|
|
|
|
requestedEndpoint = null |
|
|
|
|
val next = wantedRoute?.takeIf { it != route } |
|
|
|
|
wantedRoute = null |
|
|
|
|
next?.let { routeTo(it) } |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
return |
|
|
|
|
@ -159,16 +181,28 @@ class UtunConnection(
|
|
|
|
|
} |
|
|
|
|
return // На API 34+ один путь управления: CallEndpoint, без параллельного legacy-запроса. |
|
|
|
|
} |
|
|
|
|
val supported = callAudioState?.supportedRouteMask ?: run { |
|
|
|
|
LogManager.addLog("WARN", "UtunConn", "route waiting for audio state id=$callId requested=$route") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
val stateRoute = when (route) { |
|
|
|
|
AudioRoute.EARPIECE -> CallAudioState.ROUTE_EARPIECE |
|
|
|
|
AudioRoute.SPEAKER -> CallAudioState.ROUTE_SPEAKER |
|
|
|
|
AudioRoute.HEADSET -> if ((callAudioState?.supportedRouteMask ?: 0) and CallAudioState.ROUTE_BLUETOOTH != 0) |
|
|
|
|
AudioRoute.HEADSET -> if (supported and CallAudioState.ROUTE_BLUETOOTH != 0) |
|
|
|
|
CallAudioState.ROUTE_BLUETOOTH else CallAudioState.ROUTE_WIRED_HEADSET |
|
|
|
|
} |
|
|
|
|
if (supported and stateRoute == 0) { |
|
|
|
|
wantedRoute = null |
|
|
|
|
LogManager.addLog("WARN", "UtunConn", "route unavailable id=$callId requested=$route supported=$supported") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if (requestedLegacyRoute == stateRoute) return |
|
|
|
|
requestedLegacyRoute = stateRoute |
|
|
|
|
try { |
|
|
|
|
setAudioRoute(stateRoute) |
|
|
|
|
LogManager.addLog("INFO", "UtunConn", "audio route -> %d (route=%s)".format(stateRoute, route)) |
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
requestedLegacyRoute = 0; wantedRoute = null |
|
|
|
|
LogManager.addLog("WARN", "UtunConn", "setAudioRoute failed: %s".format(e.message)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|