Skip to content

Commit d35b165

Browse files
authored
Improve Telecom and Bluetooth Headset connectivity (#1841)
* temp * Here’s a cleaner commit message: Bump AudioSwitch and fix dropped Telecom actions 1. Bump AudioSwitch to 1.1.0, which includes Twilio 1.2.5 with BLE listener support. 2. Fix dropped Telecom actions by buffering the channel. Previously, the Bluetooth request could take up to 2 seconds, causing the call accept action to be dropped when both events arrived simultaneously. * private val * add ut * remove ut
1 parent e2ca247 commit d35b165

6 files changed

Lines changed: 58 additions & 5 deletions

File tree

‎gradle/libs.versions.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ coil = "2.6.0"
4040
landscapist = "2.4.2"
4141
accompanist = "0.34.0"
4242
telephoto = "0.3.0"
43-
audioswitch = "1.0.0"
43+
audioswitch = "1.1.0"
4444
libyuv = "0.36.0"
4545

4646
wire = "4.7.0"

‎stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ class MicrophoneManager(
710710
* Select a specific device
711711
*/
712712
fun select(device: StreamAudioDevice?) {
713-
logger.i { "selecting device $device" }
713+
logger.i { "[select] selecting device $device" }
714714
ifAudioHandlerInitialized { it.selectDevice(device?.toAudioDevice()) }
715715
_selectedDevice.value = device
716716

‎stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/TelecomCallController.kt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package io.getstream.video.android.core.notifications.internal.telecom
1919
import android.content.Context
2020
import android.telecom.DisconnectCause
2121
import io.getstream.android.video.generated.models.OwnCapability
22+
import io.getstream.log.taggedLogger
2223
import io.getstream.video.android.core.Call
2324
import io.getstream.video.android.core.StreamVideoClient
2425
import io.getstream.video.android.core.notifications.internal.telecom.jetpack.InteractionSource
@@ -29,6 +30,7 @@ import io.getstream.video.android.core.notifications.internal.telecom.jetpack.Te
2930
* Valid disconnected cause: [DisconnectCause.LOCAL, DisconnectCause.REMOTE, DisconnectCause.MISSED, or DisconnectCause.REJECTED]
3031
*/
3132
class TelecomCallController(val context: Context) {
33+
private val logger by taggedLogger("TelecomCallController")
3234
private val telecomPermissions = TelecomPermissions()
3335
private val telecomHelper = TelecomHelper()
3436

@@ -65,6 +67,9 @@ class TelecomCallController(val context: Context) {
6567
if (telecomHelper.canUseJetpackTelecom()) {
6668
val telecomCall =
6769
call.state.jetpackTelecomRepository?.currentCall?.value
70+
if (telecomCall == null) {
71+
logger.w { "[performAction] unable to perform action as currentCall is null" }
72+
}
6873
telecomCall?.let {
6974
block(telecomCall)
7075
}

‎stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/JetpackTelecomRepository.kt‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ internal class JetpackTelecomRepository(
108108
)
109109

110110
// Creates a channel to send actions to the call scope.
111-
val actionSource = Channel<TelecomCallAction>()
111+
val actionSource = Channel<TelecomCallAction>(
112+
capacity = Channel.BUFFERED,
113+
)
112114
// Register the call and handle actions in the scope
113115
try {
114116
logger.d { "[registerCall] addCall" }
@@ -269,7 +271,14 @@ internal class JetpackTelecomRepository(
269271

270272
if (newEndpoint != null) {
271273
requestEndpointChange(newEndpoint).also {
272-
logger.d { "[doSwitchEndpoint] Endpoint ${newEndpoint.name} changed: $it " }
274+
when (it) {
275+
is CallControlResult.Success -> {
276+
logger.d { "[doSwitchEndpoint] Endpoint ${newEndpoint.name} changed successfully: $it " }
277+
}
278+
is CallControlResult.Error -> {
279+
logger.w { "[doSwitchEndpoint] Endpoint ${newEndpoint.name} changed with error: $it " }
280+
}
281+
}
273282
}
274283
}
275284
}
@@ -296,6 +305,7 @@ internal class JetpackTelecomRepository(
296305
}
297306

298307
is CallControlResult.Error -> {
308+
logger.w { "[CallControlScope.doAnswer] error with ${result.errorCode}" }
299309
updateCurrentCall {
300310
TelecomCall.Unregistered(
301311
id = id,

‎stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/TelecomCall.kt‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import android.os.ParcelUuid
2020
import android.telecom.DisconnectCause
2121
import androidx.core.telecom.CallAttributesCompat
2222
import androidx.core.telecom.CallEndpointCompat
23+
import io.getstream.log.taggedLogger
2324
import kotlinx.coroutines.channels.Channel
2425

2526
/**
2627
* Custom representation of a call state.
2728
*/
2829
internal sealed class TelecomCall {
30+
val logger by taggedLogger("TelecomCall")
2931

3032
/**
3133
* There is no current or past calls in the stack
@@ -59,7 +61,9 @@ internal sealed class TelecomCall {
5961
* @return true if the action was sent, false otherwise
6062
*/
6163
fun processAction(action: TelecomCallAction): Boolean {
62-
return actionSource.trySend(action).isSuccess
64+
val result = actionSource.trySend(action).isSuccess
65+
logger.d { "[processAction] action:$action, result = $result" }
66+
return result
6367
}
6468
}
6569

‎stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/telecom/jetpack/JetpackTelecomRepositoryTest.kt‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import io.mockk.slot
3131
import io.mockk.verify
3232
import kotlinx.coroutines.CancellationException
3333
import kotlinx.coroutines.flow.emptyFlow
34+
import kotlinx.coroutines.test.StandardTestDispatcher
3435
import kotlinx.coroutines.test.runTest
3536
import org.junit.Before
3637
import org.junit.runner.RunWith
@@ -101,6 +102,39 @@ class JetpackTelecomRepositoryTest {
101102
assertTrue(repository.currentCall.value is TelecomCall.None)
102103
}
103104

105+
@Test
106+
fun `registerCall buffers actions until collector starts`() = runTest {
107+
val callControlScope = mockk<CallControlScope>(relaxed = true) {
108+
every { coroutineContext } returns
109+
backgroundScope.coroutineContext + StandardTestDispatcher(testScheduler)
110+
every { getCallId() } returns ParcelUuid(UUID.randomUUID())
111+
every { currentCallEndpoint } returns emptyFlow()
112+
every { availableEndpoints } returns emptyFlow()
113+
every { isMuted } returns emptyFlow()
114+
}
115+
coEvery {
116+
callsManager.addCall(any(), any(), any(), any(), any(), any())
117+
} coAnswers {
118+
arg<CallControlScope.() -> Unit>(5).invoke(callControlScope)
119+
}
120+
val sendResults = mutableListOf<Boolean>()
121+
122+
repository.registerCall(
123+
displayName = "Caller",
124+
address = Uri.parse("stream:call-id"),
125+
isIncoming = true,
126+
isVideoCall = false,
127+
onRegistered = {
128+
val registeredCall = repository.currentCall.value as TelecomCall.Registered
129+
sendResults += registeredCall.processAction(TelecomCallAction.ToggleMute(true))
130+
sendResults += registeredCall.processAction(TelecomCallAction.ToggleMute(false))
131+
},
132+
onException = { throw AssertionError("Unexpected registration failure", it) },
133+
)
134+
135+
assertEquals(listOf(true, true), sendResults)
136+
}
137+
104138
@Test
105139
fun `registerCall reports non-cancellation failures`() = runTest {
106140
val failure = IllegalStateException("Telecom rejected call")

0 commit comments

Comments
 (0)