Skip to content

Commit 6f7c9d2

Browse files
committed
refactor(core): extract incoming call coordinator
1 parent bc5ae2f commit 6f7c9d2

4 files changed

Lines changed: 205 additions & 75 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.core.notifications.internal.service
18+
19+
import android.app.Notification
20+
import io.getstream.video.android.model.StreamCallId
21+
22+
internal data class IncomingCallRequest(
23+
val callId: StreamCallId,
24+
val callDisplayName: String?,
25+
val callServiceConfiguration: CallServiceConfig,
26+
val isVideo: Boolean,
27+
val payload: Map<String, Any?>,
28+
val notification: Notification?,
29+
)

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

Lines changed: 22 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,19 @@ package io.getstream.video.android.core.notifications.internal.service
3535
import android.annotation.SuppressLint
3636
import android.app.Notification
3737
import android.os.Bundle
38-
import androidx.core.app.NotificationManagerCompat
3938
import androidx.core.content.ContextCompat
4039
import androidx.core.net.toUri
4140
import io.getstream.log.taggedLogger
4241
import io.getstream.video.android.core.Call
4342
import io.getstream.video.android.core.StreamVideoClient
44-
import io.getstream.video.android.core.notifications.NotificationType
4543
import io.getstream.video.android.core.notifications.internal.Throttler
4644
import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT
47-
import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL
45+
import io.getstream.video.android.core.notifications.internal.service.incomingcallcoordinator.IncomingCallCoordinator
46+
import io.getstream.video.android.core.notifications.internal.service.incomingcallcoordinator.PreAndroid17IncomingCallCoordinator
4847
import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper
4948
import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions
5049
import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall
5150
import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCallAction
52-
import io.getstream.video.android.core.utils.safeCallWithResult
5351
import io.getstream.video.android.model.StreamCallId
5452
import kotlinx.coroutines.Dispatchers
5553
import kotlinx.coroutines.delay
@@ -66,6 +64,16 @@ internal class ServiceLauncher(private val client: StreamVideoClient) {
6664
private val telecomPermissions = TelecomPermissions()
6765
private val jetpackTelecomRepositoryProvider = JetpackTelecomRepositoryProvider(client)
6866
private val throttler = Throttler()
67+
private val incomingCallCoordinator: IncomingCallCoordinator =
68+
PreAndroid17IncomingCallCoordinator(
69+
context = context,
70+
client = client,
71+
incomingCallPresenter = incomingCallPresenter,
72+
serviceIntentBuilder = serviceIntentBuilder,
73+
telecomPermissions = telecomPermissions,
74+
telecomHelper = telecomHelper,
75+
jetpackTelecomRepositoryProvider = jetpackTelecomRepositoryProvider,
76+
)
6977

7078
@SuppressLint("MissingPermission", "NewApi")
7179
fun showIncomingCall(
@@ -76,43 +84,16 @@ internal class ServiceLauncher(private val client: StreamVideoClient) {
7684
payload: Map<String, Any?>,
7785
notification: Notification?,
7886
) {
79-
val result = incomingCallPresenter.showIncomingCall(
80-
context,
81-
callId,
82-
callDisplayName,
83-
callServiceConfiguration,
84-
notification,
87+
incomingCallCoordinator.showIncomingCall(
88+
IncomingCallRequest(
89+
callId = callId,
90+
callDisplayName = callDisplayName,
91+
callServiceConfiguration = callServiceConfiguration,
92+
isVideo = isVideo,
93+
payload = payload,
94+
notification = notification,
95+
),
8596
)
86-
logger.d { "[showIncomingCall] service start result: $result" }
87-
if (telecomPermissions.canUseTelecom(callServiceConfiguration, context)) {
88-
if (telecomHelper.canUseJetpackTelecom()) {
89-
when (result) {
90-
ShowIncomingCallResult.FG_SERVICE -> {
91-
updateIncomingCallNotification(notification, callId)
92-
93-
val jetpackTelecomRepository = jetpackTelecomRepositoryProvider.get(callId)
94-
95-
val appSchema = client.telecomConfig?.schema
96-
val addressUri = "$appSchema:${callId.id}".toUri()
97-
val formattedCallDisplayName = callDisplayName?.takeIf { it.isNotBlank() } ?: DEFAULT_CALL_TEXT
98-
99-
val call = client.call(callId.type, callId.id)
100-
101-
call.state.jetpackTelecomRepository = (jetpackTelecomRepository)
102-
103-
call.scope.launch {
104-
jetpackTelecomRepository.registerCall(
105-
formattedCallDisplayName,
106-
addressUri,
107-
true,
108-
isVideo,
109-
)
110-
}
111-
}
112-
else -> {}
113-
}
114-
}
115-
}
11697
}
11798

11899
fun showOnGoingCall(call: Call, trigger: String) {
@@ -187,45 +168,11 @@ internal class ServiceLauncher(private val client: StreamVideoClient) {
187168
}
188169
}
189170

190-
/**
191-
* Because we need to retrieve the notification
192-
* in [io.getstream.video.android.core.notifications.internal.telecom.connection.SuccessIncomingTelecomConnection]
193-
*/
194-
private fun updateIncomingCallNotification(
195-
notification: Notification?,
196-
callId: StreamCallId,
197-
) {
198-
notification?.let {
199-
val notificationId = callId.getNotificationId(NotificationType.Incoming)
200-
client.call(callId.type, callId.id)
201-
.state.updateNotification(notificationId, notification)
202-
}
203-
}
204-
205171
fun removeIncomingCall(
206172
callId: StreamCallId,
207173
config: CallServiceConfig = DefaultCallConfigurations.default,
208174
) {
209-
safeCallWithResult {
210-
context.startService(
211-
serviceIntentBuilder.buildStartIntent(
212-
context,
213-
StartServiceParam(
214-
callId,
215-
TRIGGER_REMOVE_INCOMING_CALL,
216-
callServiceConfiguration = config,
217-
),
218-
),
219-
)!!
220-
}.onError {
221-
logger.d {
222-
"[removeIncomingCall] notificationId: ${callId.getNotificationId(
223-
NotificationType.Incoming,
224-
)}"
225-
}
226-
NotificationManagerCompat.from(context)
227-
.cancel(callId.getNotificationId(NotificationType.Incoming))
228-
}
175+
incomingCallCoordinator.dismissIncomingCall(callId, config)
229176
}
230177

231178
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.core.notifications.internal.service.incomingcallcoordinator
18+
19+
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig
20+
import io.getstream.video.android.core.notifications.internal.service.IncomingCallRequest
21+
import io.getstream.video.android.model.StreamCallId
22+
23+
internal interface IncomingCallCoordinator {
24+
25+
fun showIncomingCall(request: IncomingCallRequest)
26+
27+
fun dismissIncomingCall(
28+
callId: StreamCallId,
29+
config: CallServiceConfig,
30+
)
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.core.notifications.internal.service.incomingcallcoordinator
18+
19+
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import androidx.core.app.NotificationManagerCompat
22+
import androidx.core.net.toUri
23+
import io.getstream.log.taggedLogger
24+
import io.getstream.video.android.core.StreamVideoClient
25+
import io.getstream.video.android.core.notifications.NotificationType
26+
import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT
27+
import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL
28+
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfig
29+
import io.getstream.video.android.core.notifications.internal.service.IncomingCallPresenter
30+
import io.getstream.video.android.core.notifications.internal.service.IncomingCallRequest
31+
import io.getstream.video.android.core.notifications.internal.service.JetpackTelecomRepositoryProvider
32+
import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder
33+
import io.getstream.video.android.core.notifications.internal.service.ShowIncomingCallResult
34+
import io.getstream.video.android.core.notifications.internal.service.StartServiceParam
35+
import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper
36+
import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions
37+
import io.getstream.video.android.core.utils.safeCallWithResult
38+
import io.getstream.video.android.model.StreamCallId
39+
import kotlinx.coroutines.launch
40+
41+
/** Coordinates the existing incoming-call service and optional Telecom registration path. */
42+
internal class PreAndroid17IncomingCallCoordinator(
43+
private val context: Context,
44+
private val client: StreamVideoClient,
45+
private val incomingCallPresenter: IncomingCallPresenter,
46+
private val serviceIntentBuilder: ServiceIntentBuilder,
47+
private val telecomPermissions: TelecomPermissions,
48+
private val telecomHelper: TelecomHelper,
49+
private val jetpackTelecomRepositoryProvider: JetpackTelecomRepositoryProvider,
50+
) : IncomingCallCoordinator {
51+
52+
private val logger by taggedLogger("PreAndroid17IncomingCallCoordinator")
53+
54+
@SuppressLint("MissingPermission", "NewApi")
55+
override fun showIncomingCall(request: IncomingCallRequest) {
56+
val result = incomingCallPresenter.showIncomingCall(
57+
context = context,
58+
callId = request.callId,
59+
callDisplayName = request.callDisplayName,
60+
callServiceConfiguration = request.callServiceConfiguration,
61+
notification = request.notification,
62+
)
63+
logger.d { "[showIncomingCall] service start result: $result" }
64+
65+
if (result != ShowIncomingCallResult.FG_SERVICE ||
66+
!telecomPermissions.canUseTelecom(request.callServiceConfiguration, context) ||
67+
!telecomHelper.canUseJetpackTelecom()
68+
) {
69+
return
70+
}
71+
72+
updateIncomingCallNotification(request, client)
73+
val jetpackTelecomRepository = jetpackTelecomRepositoryProvider.get(request.callId)
74+
val addressUri = "${client.telecomConfig?.schema}:${request.callId.id}".toUri()
75+
val formattedCallDisplayName = request.callDisplayName
76+
?.takeIf { it.isNotBlank() }
77+
?: DEFAULT_CALL_TEXT
78+
val call = client.call(request.callId.type, request.callId.id)
79+
80+
call.state.jetpackTelecomRepository = jetpackTelecomRepository
81+
call.scope.launch {
82+
jetpackTelecomRepository.registerCall(
83+
displayName = formattedCallDisplayName,
84+
address = addressUri,
85+
isIncoming = true,
86+
isVideoCall = request.isVideo,
87+
)
88+
}
89+
}
90+
91+
override fun dismissIncomingCall(
92+
callId: StreamCallId,
93+
config: CallServiceConfig,
94+
) {
95+
safeCallWithResult {
96+
context.startService(
97+
serviceIntentBuilder.buildStartIntent(
98+
context,
99+
StartServiceParam(
100+
callId = callId,
101+
trigger = TRIGGER_REMOVE_INCOMING_CALL,
102+
callServiceConfiguration = config,
103+
),
104+
),
105+
)!!
106+
}.onError {
107+
val notificationId = callId.getNotificationId(NotificationType.Incoming)
108+
logger.d { "[dismissIncomingCall] notificationId: $notificationId" }
109+
NotificationManagerCompat.from(context).cancel(notificationId)
110+
}
111+
}
112+
113+
private fun updateIncomingCallNotification(
114+
request: IncomingCallRequest,
115+
client: StreamVideoClient,
116+
) {
117+
request.notification?.let { notification ->
118+
val notificationId = request.callId.getNotificationId(NotificationType.Incoming)
119+
client.call(request.callId.type, request.callId.id)
120+
.state.updateNotification(notificationId, notification)
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)