Skip to content

Commit ead5fd8

Browse files
authored
fix(core): register app with Telecom once
1 parent 93baaa7 commit ead5fd8

7 files changed

Lines changed: 50 additions & 72 deletions

File tree

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class ClientState(private val client: StreamVideo) {
8686
public val activeCall: StateFlow<Call?> = _activeCall
8787

8888
public val callConfigRegistry = (client as StreamVideoClient).callServiceConfigRegistry
89-
private val serviceLauncher = ServiceLauncher(client.context)
89+
internal val serviceLauncher = ServiceLauncher(client.context, streamVideoClient)
9090

9191
internal val clientEventReporter = (client as StreamVideoClient).analytics.clientEventReporter
9292

@@ -293,13 +293,11 @@ class ClientState(private val client: StreamVideo) {
293293
CallService.TRIGGER_ONGOING_CALL -> serviceLauncher.showOnGoingCall(
294294
call,
295295
trigger,
296-
streamVideoClient,
297296
)
298297

299298
CallService.TRIGGER_OUTGOING_CALL -> serviceLauncher.showOutgoingCall(
300299
call,
301300
trigger,
302-
streamVideoClient,
303301
)
304302

305303
else -> {}
@@ -312,10 +310,7 @@ class ClientState(private val client: StreamVideo) {
312310
internal fun maybeStopForegroundService(call: Call) {
313311
val callConfig = streamVideoClient.callServiceConfigRegistry.get(call.type)
314312
if (callConfig.runCallServiceInForeground) {
315-
val context = streamVideoClient.context
316-
317313
logger.d { "Building stop intent for call_id: ${call.cid}" }
318-
val serviceLauncher = ServiceLauncher(context)
319314
serviceLauncher.stopService(call)
320315
}
321316
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import android.os.Bundle
2222
import io.getstream.log.taggedLogger
2323
import io.getstream.result.Result
2424
import io.getstream.video.android.core.model.RejectReason
25-
import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher
2625
import io.getstream.video.android.core.notifications.internal.telecom.TelecomCallController
2726
import io.getstream.video.android.model.StreamCallId
2827

@@ -51,9 +50,7 @@ internal class ExternalCallRejectionHandler() {
5150
}
5251
logger.d { "[onRejectCall] source:$source, #ringing; callId: ${call.id}, action: ${intent.action}" }
5352

54-
val serviceLauncher = ServiceLauncher(context)
55-
serviceLauncher.removeIncomingCall(
56-
context,
53+
StreamVideo.instance().state.serviceLauncher.removeIncomingCall(
5754
StreamCallId.fromCallCid(call.cid),
5855
StreamVideo.instance().state.callConfigRegistry.get(call.type),
5956
)

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/DefaultNotificationHandler.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import io.getstream.video.android.core.notifications.NotificationHandler.Compani
4848
import io.getstream.video.android.core.notifications.NotificationHandler.Companion.ACTION_NOTIFICATION
4949
import io.getstream.video.android.core.notifications.dispatchers.DefaultNotificationDispatcher
5050
import io.getstream.video.android.core.notifications.dispatchers.NotificationDispatcher
51-
import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher
5251
import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationConfig
5352
import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationContent
5453
import io.getstream.video.android.core.notifications.medianotifications.MediaNotificationVisuals
@@ -88,7 +87,6 @@ public open class DefaultNotificationHandler(
8887
private val logger by taggedLogger("Call:NotificationHandler")
8988
val intentResolver =
9089
DefaultStreamIntentResolver(application, DefaultNotificationIntentBundleResolver())
91-
private val serviceLauncher = ServiceLauncher(application)
9290

9391
protected val notificationManager: NotificationManagerCompat by lazy {
9492
NotificationManagerCompat.from(application).also {
@@ -114,14 +112,12 @@ public open class DefaultNotificationHandler(
114112
) {
115113
logger.d { "[onRingingCall] #ringing; callId: ${callId.id}" }
116114
val streamVideo = StreamVideo.instance()
117-
serviceLauncher.showIncomingCall(
118-
application,
115+
streamVideo.state.serviceLauncher.showIncomingCall(
119116
callId,
120117
callDisplayName,
121118
streamVideo.state.callConfigRegistry.get(callId.type),
122119
isVideo = isVideoCall(callId, payload),
123120
payload = payload,
124-
streamVideo,
125121
notification = getRingingCallNotification(
126122
RingingState.Incoming(),
127123
callId,

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/handlers/StreamDefaultNotificationHandler.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import io.getstream.video.android.core.notifications.dispatchers.DefaultNotifica
5757
import io.getstream.video.android.core.notifications.dispatchers.NotificationDispatcher
5858
import io.getstream.video.android.core.notifications.extractor.DefaultNotificationContentExtractor
5959
import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL
60-
import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher
6160
import io.getstream.video.android.core.notifications.style.StyleProvider
6261
import io.getstream.video.android.core.utils.BackgroundRestrictions
6362
import io.getstream.video.android.core.utils.isAppInForeground
@@ -149,7 +148,6 @@ constructor(
149148
NotificationPermissionHandler by notificationPermissionHandler {
150149

151150
private val logger by taggedLogger("Video:StreamNotificationHandler")
152-
private val serviceLauncher = ServiceLauncher(application)
153151
private val styleProvider = StyleProvider(application)
154152
private val batteryRestrictions = BackgroundRestrictions(application)
155153

@@ -177,14 +175,12 @@ constructor(
177175
val canRunService =
178176
streamVideo.callServiceConfigRegistry.get(callId.type).runCallServiceInForeground
179177
if (canRunService) {
180-
serviceLauncher.showIncomingCall(
181-
application,
178+
streamVideo.state.serviceLauncher.showIncomingCall(
182179
callId,
183180
callDisplayName,
184181
streamVideo.state.callConfigRegistry.get(callId.type),
185182
isVideo = isVideoCall(callId, payload),
186183
payload = payload,
187-
streamVideo,
188184
notification = getRingingCallNotification(
189185
RingingState.Incoming(),
190186
callId,

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/JetpackTelecomRepositoryProvider.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package io.getstream.video.android.core.notifications.internal.service
1818

19-
import android.content.Context
2019
import android.os.Build
2120
import androidx.annotation.RequiresApi
2221
import androidx.core.telecom.CallsManager
@@ -25,20 +24,30 @@ import io.getstream.video.android.core.notifications.internal.telecom.IncomingCa
2524
import io.getstream.video.android.core.notifications.internal.telecom.jetpack.JetpackTelecomRepository
2625
import io.getstream.video.android.model.StreamCallId
2726

28-
internal class JetpackTelecomRepositoryProvider(private val context: Context) {
27+
internal class JetpackTelecomRepositoryProvider(private val streamVideo: StreamVideo) {
28+
29+
private val context = streamVideo.context.applicationContext ?: streamVideo.context
30+
private val registrationLock = Any()
31+
private lateinit var callsManager: CallsManager
2932

3033
@RequiresApi(Build.VERSION_CODES.O)
3134
fun get(callId: StreamCallId): JetpackTelecomRepository {
32-
val callsManager = CallsManager(context).apply {
33-
registerAppWithTelecom(
34-
capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING and
35-
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING,
36-
)
37-
}
38-
39-
val streamVideo = StreamVideo.instance()
35+
val callsManager = getOrRegisterCallsManager()
4036
val incomingCallTelecomAction =
4137
IncomingCallTelecomAction(streamVideo)
4238
return JetpackTelecomRepository(callsManager, callId, incomingCallTelecomAction)
4339
}
40+
41+
@RequiresApi(Build.VERSION_CODES.O)
42+
private fun getOrRegisterCallsManager(): CallsManager = synchronized(registrationLock) {
43+
if (!::callsManager.isInitialized) {
44+
callsManager = CallsManager(context).also { manager ->
45+
manager.registerAppWithTelecom(
46+
capabilities = CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING or
47+
CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING,
48+
)
49+
}
50+
}
51+
callsManager
52+
}
4453
}

stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncher.kt

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import androidx.core.content.ContextCompat
4141
import androidx.core.net.toUri
4242
import io.getstream.log.taggedLogger
4343
import io.getstream.video.android.core.Call
44-
import io.getstream.video.android.core.StreamVideo
4544
import io.getstream.video.android.core.StreamVideoClient
4645
import io.getstream.video.android.core.notifications.NotificationType
4746
import io.getstream.video.android.core.notifications.internal.Throttler
@@ -57,25 +56,26 @@ import kotlinx.coroutines.Dispatchers
5756
import kotlinx.coroutines.delay
5857
import kotlinx.coroutines.launch
5958

60-
internal class ServiceLauncher(val context: Context) {
59+
internal class ServiceLauncher(
60+
val context: Context,
61+
private val client: StreamVideoClient,
62+
) {
6163

6264
private val logger by taggedLogger("ServiceTriggers")
6365
private val serviceIntentBuilder = ServiceIntentBuilder()
6466
private val incomingCallPresenter = IncomingCallPresenter(serviceIntentBuilder)
6567
private val telecomHelper = TelecomHelper()
6668
private val telecomPermissions = TelecomPermissions()
67-
private val jetpackTelecomRepositoryProvider = JetpackTelecomRepositoryProvider(context)
69+
private val jetpackTelecomRepositoryProvider = JetpackTelecomRepositoryProvider(client)
6870
private val throttler = Throttler()
6971

7072
@SuppressLint("MissingPermission", "NewApi")
7173
fun showIncomingCall(
72-
context: Context,
7374
callId: StreamCallId,
7475
callDisplayName: String?,
7576
callServiceConfiguration: CallServiceConfig,
7677
isVideo: Boolean,
7778
payload: Map<String, Any?>,
78-
streamVideo: StreamVideo,
7979
notification: Notification?,
8080
) {
8181
val result = incomingCallPresenter.showIncomingCall(
@@ -90,15 +90,15 @@ internal class ServiceLauncher(val context: Context) {
9090
if (telecomHelper.canUseJetpackTelecom()) {
9191
when (result) {
9292
ShowIncomingCallResult.FG_SERVICE -> {
93-
updateIncomingCallNotification(notification, streamVideo, callId)
93+
updateIncomingCallNotification(notification, callId)
9494

9595
val jetpackTelecomRepository = jetpackTelecomRepositoryProvider.get(callId)
9696

97-
val appSchema = (streamVideo as StreamVideoClient).telecomConfig?.schema
97+
val appSchema = client.telecomConfig?.schema
9898
val addressUri = "$appSchema:${callId.id}".toUri()
9999
val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT
100100

101-
val call = streamVideo.call(callId.type, callId.id)
101+
val call = client.call(callId.type, callId.id)
102102

103103
call.state.jetpackTelecomRepository = (jetpackTelecomRepository)
104104

@@ -117,14 +117,12 @@ internal class ServiceLauncher(val context: Context) {
117117
}
118118
}
119119

120-
fun showOnGoingCall(call: Call, trigger: String, streamVideo: StreamVideo) {
121-
val client = streamVideo as StreamVideoClient
120+
fun showOnGoingCall(call: Call, trigger: String) {
122121
val callConfig = client.callServiceConfigRegistry.get(call.type)
123122
if (!callConfig.runCallServiceInForeground) {
124123
return
125124
}
126125
val callId = StreamCallId.fromCallCid(call.cid)
127-
val context = client.context
128126
val serviceIntent = ServiceIntentBuilder().buildStartIntent(
129127
context,
130128
StartServiceParam(
@@ -137,8 +135,8 @@ internal class ServiceLauncher(val context: Context) {
137135
}
138136

139137
@SuppressLint("NewApi")
140-
fun showOutgoingCall(call: Call, trigger: String, streamVideo: StreamVideo) {
141-
val callConfig = (streamVideo as StreamVideoClient).callServiceConfigRegistry.get(call.type)
138+
fun showOutgoingCall(call: Call, trigger: String) {
139+
val callConfig = client.callServiceConfigRegistry.get(call.type)
142140
if (!callConfig.runCallServiceInForeground) {
143141
return
144142
}
@@ -165,7 +163,7 @@ internal class ServiceLauncher(val context: Context) {
165163
if (telecomHelper.canUseJetpackTelecom()) {
166164
val jetpackTelecomRepository = jetpackTelecomRepositoryProvider.get(callId)
167165

168-
val appSchema = streamVideo.telecomConfig?.schema
166+
val appSchema = client.telecomConfig?.schema
169167
val addressUri = "$appSchema:${callId.id}".toUri()
170168
val formattedCallDisplayName =
171169
callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT
@@ -199,18 +197,16 @@ internal class ServiceLauncher(val context: Context) {
199197
*/
200198
private fun updateIncomingCallNotification(
201199
notification: Notification?,
202-
streamVideo: StreamVideo,
203200
callId: StreamCallId,
204201
) {
205202
notification?.let {
206203
val notificationId = callId.getNotificationId(NotificationType.Incoming)
207-
streamVideo.call(callId.type, callId.id)
204+
client.call(callId.type, callId.id)
208205
.state.updateNotification(notificationId, notification)
209206
}
210207
}
211208

212209
fun removeIncomingCall(
213-
context: Context,
214210
callId: StreamCallId,
215211
config: CallServiceConfig = DefaultCallConfigurations.default,
216212
) {
@@ -249,25 +245,18 @@ internal class ServiceLauncher(val context: Context) {
249245

250246
private fun stopCallServiceInternal(call: Call) {
251247
logger.d { "[stopCallServiceInternal]" }
252-
val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoClient
253-
streamVideo?.let { streamVideoClient ->
254-
val callConfig = streamVideoClient.callServiceConfigRegistry.get(call.type)
255-
if (callConfig.runCallServiceInForeground) {
256-
val context = streamVideoClient.context
257-
258-
val serviceIntent = serviceIntentBuilder.buildStopIntent(
259-
context,
260-
StopServiceParam(call, callConfig),
261-
)
262-
serviceIntent?.let {
263-
logger.d {
264-
"Building stop intent, class: ${serviceIntent.component?.className} for call_id: ${call.cid}"
265-
}
266-
serviceIntent.extras?.let {
267-
logBundle(it)
268-
}
269-
context.startService(serviceIntent)
248+
val callConfig = client.callServiceConfigRegistry.get(call.type)
249+
if (callConfig.runCallServiceInForeground) {
250+
val serviceIntent = serviceIntentBuilder.buildStopIntent(
251+
context,
252+
StopServiceParam(call, callConfig),
253+
)
254+
serviceIntent?.let {
255+
logger.d {
256+
"Building stop intent, class: ${serviceIntent.component?.className} for call_id: ${call.cid}"
270257
}
258+
serviceIntent.extras?.let { logBundle(it) }
259+
context.startService(serviceIntent)
271260
}
272261
}
273262
}

stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/notifications/internal/service/ServiceLauncherTest.kt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class ServiceLauncherTest {
123123
every { StreamVideo.instance() } returns streamVideo
124124
every { jetpackTelecomRepositoryProvider.get(any()) } returns jetpackTelecomRepository
125125

126-
serviceLauncher = ServiceLauncher(context)
126+
serviceLauncher = ServiceLauncher(context, streamVideo)
127127
}
128128

129129
@After
@@ -153,13 +153,11 @@ class ServiceLauncherTest {
153153
} returns mockk()
154154

155155
serviceLauncher.showIncomingCall(
156-
context = context,
157156
callId = callId,
158157
callDisplayName = "Test Caller",
159158
callServiceConfiguration = callServiceConfig,
160159
isVideo = true,
161160
payload = emptyMap(),
162-
streamVideo = streamVideo,
163161
notification = notification,
164162
)
165163
testScheduler.advanceUntilIdle()
@@ -172,13 +170,11 @@ class ServiceLauncherTest {
172170
every { anyConstructed<TelecomPermissions>().canUseTelecom(any(), any()) } returns false
173171

174172
serviceLauncher.showIncomingCall(
175-
context,
176173
callId,
177174
"Test Caller",
178175
callServiceConfig,
179176
isVideo = false,
180177
payload = emptyMap(),
181-
streamVideo = streamVideo,
182178
notification = notification,
183179
)
184180

@@ -204,7 +200,7 @@ class ServiceLauncherTest {
204200
every { call.cid } returns "default:cid-123"
205201
every { call.isVideoEnabled() } returns true
206202

207-
serviceLauncher.showOutgoingCall(call, "outgoing_call", streamVideo)
203+
serviceLauncher.showOutgoingCall(call, "outgoing_call")
208204

209205
verify { ContextCompat.startForegroundService(context, any<Intent>()) }
210206

@@ -229,7 +225,7 @@ class ServiceLauncherTest {
229225
every { call.isVideoEnabled() } returns true
230226
every { anyConstructed<TelecomPermissions>().canUseTelecom(any(), any()) } returns false
231227

232-
serviceLauncher.showOutgoingCall(call, "outgoing_call", streamVideo)
228+
serviceLauncher.showOutgoingCall(call, "outgoing_call")
233229

234230
coVerify(exactly = 0) { jetpackTelecomRepository.registerCall(any(), any(), any(), any()) }
235231
}

0 commit comments

Comments
 (0)