Skip to content

Commit f6fdcbf

Browse files
committed
refactor(core): extract incoming call coordinator
1 parent b74990b commit f6fdcbf

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
@@ -36,21 +36,19 @@ import android.annotation.SuppressLint
3636
import android.app.Notification
3737
import android.content.Context
3838
import android.os.Bundle
39-
import androidx.core.app.NotificationManagerCompat
4039
import androidx.core.content.ContextCompat
4140
import androidx.core.net.toUri
4241
import io.getstream.log.taggedLogger
4342
import io.getstream.video.android.core.Call
4443
import io.getstream.video.android.core.StreamVideoClient
45-
import io.getstream.video.android.core.notifications.NotificationType
4644
import io.getstream.video.android.core.notifications.internal.Throttler
4745
import io.getstream.video.android.core.notifications.internal.VideoPushDelegate.Companion.DEFAULT_CALL_TEXT
48-
import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_REMOVE_INCOMING_CALL
46+
import io.getstream.video.android.core.notifications.internal.service.incomingcallcoordinator.IncomingCallCoordinator
47+
import io.getstream.video.android.core.notifications.internal.service.incomingcallcoordinator.PreAndroid17IncomingCallCoordinator
4948
import io.getstream.video.android.core.notifications.internal.telecom.TelecomHelper
5049
import io.getstream.video.android.core.notifications.internal.telecom.TelecomPermissions
5150
import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCall
5251
import io.getstream.video.android.core.notifications.internal.telecom.jetpack.TelecomCallAction
53-
import io.getstream.video.android.core.utils.safeCallWithResult
5452
import io.getstream.video.android.model.StreamCallId
5553
import kotlinx.coroutines.Dispatchers
5654
import kotlinx.coroutines.delay
@@ -68,6 +66,16 @@ internal class ServiceLauncher(
6866
private val telecomPermissions = TelecomPermissions()
6967
private val jetpackTelecomRepositoryProvider = JetpackTelecomRepositoryProvider(client)
7068
private val throttler = Throttler()
69+
private val incomingCallCoordinator: IncomingCallCoordinator =
70+
PreAndroid17IncomingCallCoordinator(
71+
context = context,
72+
client = client,
73+
incomingCallPresenter = incomingCallPresenter,
74+
serviceIntentBuilder = serviceIntentBuilder,
75+
telecomPermissions = telecomPermissions,
76+
telecomHelper = telecomHelper,
77+
jetpackTelecomRepositoryProvider = jetpackTelecomRepositoryProvider,
78+
)
7179

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

120101
fun showOnGoingCall(call: Call, trigger: String) {
@@ -191,45 +172,11 @@ internal class ServiceLauncher(
191172
}
192173
}
193174

194-
/**
195-
* Because we need to retrieve the notification
196-
* in [io.getstream.video.android.core.notifications.internal.telecom.connection.SuccessIncomingTelecomConnection]
197-
*/
198-
private fun updateIncomingCallNotification(
199-
notification: Notification?,
200-
callId: StreamCallId,
201-
) {
202-
notification?.let {
203-
val notificationId = callId.getNotificationId(NotificationType.Incoming)
204-
client.call(callId.type, callId.id)
205-
.state.updateNotification(notificationId, notification)
206-
}
207-
}
208-
209175
fun removeIncomingCall(
210176
callId: StreamCallId,
211177
config: CallServiceConfig = DefaultCallConfigurations.default,
212178
) {
213-
safeCallWithResult {
214-
context.startService(
215-
serviceIntentBuilder.buildStartIntent(
216-
context,
217-
StartServiceParam(
218-
callId,
219-
TRIGGER_REMOVE_INCOMING_CALL,
220-
callServiceConfiguration = config,
221-
),
222-
),
223-
)!!
224-
}.onError {
225-
logger.d {
226-
"[removeIncomingCall] notificationId: ${callId.getNotificationId(
227-
NotificationType.Incoming,
228-
)}"
229-
}
230-
NotificationManagerCompat.from(context)
231-
.cancel(callId.getNotificationId(NotificationType.Incoming))
232-
}
179+
incomingCallCoordinator.dismissIncomingCall(callId, config)
233180
}
234181

235182
/**
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)