Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,20 @@ object StreamVideoInitHelper {
)
}

override fun getOngoingCallBundle(
callId: StreamCallId,
notificationId: Int,
payload: Map<String, Any?>,
): Bundle {
return StreamCallActivity.callIntentBundle(
callId,
configuration = StreamCallActivityConfiguration(
closeScreenOnCallEnded = true,
),
leaveWhenLastInCall = true,
)
}

override fun getAcceptCallBundle(
callId: StreamCallId,
notificationId: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@

CallRecordingStartedEvent.RecordingType.Raw -> _rawRecording.value = true
CallRecordingStartedEvent.RecordingType.Composite -> _compositeRecording.value = true
else -> {}

Check warning on line 979 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either remove or fill this block of code.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebj_&open=AZzSUoFIuIJseWZVebj_&pullRequest=1627
}
}

Expand All @@ -988,7 +988,7 @@
_rawRecording.value = false
CallRecordingStoppedEvent.RecordingType.Composite ->
_compositeRecording.value = false
else -> {}

Check warning on line 991 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either remove or fill this block of code.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebkA&open=AZzSUoFIuIJseWZVebkA&pullRequest=1627
}
// Only set recording=false when ALL recording types are inactive
_recording.value = _compositeRecording.value ||
Expand Down Expand Up @@ -1270,7 +1270,7 @@
return
}

else -> {}

Check warning on line 1273 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either remove or fill this block of code.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZ1H6h8ra8C5DzI8rWHK&open=AZ1H6h8ra8C5DzI8rWHK&pullRequest=1627
}

// this is only true when we are in the session (we have accepted/joined the call)
Expand All @@ -1287,7 +1287,7 @@
_session.value?.participants?.find { it.user.id == client.userId } != null
val outgoingMembersCount = _members.value.filter { it.value.user.id != client.userId }.size
val isCallEnded: Boolean = _endedAt.value != null
val createdBySelf = createdBy?.id == client.userId

Check warning on line 1290 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused "createdBySelf" local variable.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebkD&open=AZzSUoFIuIJseWZVebkD&pullRequest=1627

ringingLogger.d { "Current: ${_ringingState.value}, call_id: ${call.cid}" }

Expand All @@ -1304,7 +1304,7 @@
ringingLogger.d { "call_id: ${call.cid}, Flags: $ringingStateLogs" }

// no members - call is empty, we can join
val state: RingingState = if (hasActiveCall && !isJoinAndRingInProgress.get()) {

Check warning on line 1307 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge chained "if" statements into a single "when" statement.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebkE&open=AZzSUoFIuIJseWZVebkE&pullRequest=1627
/**
* Normal join, not joinAndRing
*/
Expand Down Expand Up @@ -1791,7 +1791,7 @@
}

@Deprecated("Use updateNotification(Int, Notification) instead")
fun updateNotification(notification: Notification) {

Check warning on line 1794 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebkC&open=AZzSUoFIuIJseWZVebkC&pullRequest=1627
atomicNotification.set(notification)
}

Expand All @@ -1800,6 +1800,11 @@
this.atomicNotification.set(notification)
}

@InternalStreamVideoApi
fun setOwnCapabilities(ownCapability: List<OwnCapability>) {
this._ownCapabilities.value = ownCapability
}

/**
* [RingingState.Incoming] and [RingingState.Outgoing] are intentionally not observed.
* In Android Telecom, hold states are only applicable once a call is active (answered).
Expand All @@ -1807,17 +1812,17 @@
private fun observeTelecomHold(repo: JetpackTelecomRepository) {
telecomHoldObserverJob?.cancel()

telecomHoldObserverJob = scope.launch(Dispatchers.Default) {

Check warning on line 1815 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid hardcoded dispatchers.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebj-&open=AZzSUoFIuIJseWZVebj-&pullRequest=1627
repo.currentCall
.map { (it as? TelecomCall.Registered)?.isOnHold == true }
.distinctUntilChanged()
.filter { it }
.collect { isOnHold ->

Check warning on line 1820 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "_" instead of this unused lambda parameter "isOnHold".

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebj9&open=AZzSUoFIuIJseWZVebj9&pullRequest=1627
when (ringingState.value) {
is RingingState.Active -> {
call.leave("call-on-hold")
}
else -> {}

Check warning on line 1825 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either remove or fill this block of code.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoFIuIJseWZVebkB&open=AZzSUoFIuIJseWZVebkB&pullRequest=1627
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.getstream.result.Error
import io.getstream.video.android.core.internal.InternalStreamVideoApi
import io.getstream.video.android.core.notifications.internal.service.CallService
import io.getstream.video.android.core.notifications.internal.service.ServiceIntentBuilder
import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher
import io.getstream.video.android.core.notifications.internal.telecom.TelecomIntegrationType
import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState
Expand Down Expand Up @@ -184,9 +185,16 @@
if (!call.state.isJoinAndRingInProgress.get()) {
transitionToAcceptCall(call)
}
call.scope.launch {
delay(serviceTransitionDelayMs)
maybeStartForegroundService(call, CallService.TRIGGER_ONGOING_CALL)
// Intentionally skipping maybeStartForegroundService because service should already be started
// when initiating outgoing-call
val callServiceConfig = callConfigRegistry.get(call.type)
val serviceClass = callServiceConfig.serviceClass
val isServiceRunning = ServiceIntentBuilder()
.isServiceRunning(this.client.context, serviceClass)
if (callServiceConfig.runCallServiceInForeground) {

Check warning on line 194 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this "if" statement with the nested one.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoHbuIJseWZVebkF&open=AZzSUoHbuIJseWZVebkF&pullRequest=1627
if (!isServiceRunning) {
logger.e { "Outgoing call service should already be running" }
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
else -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import io.getstream.video.android.core.StreamVideo
import io.getstream.video.android.core.StreamVideoClient
import io.getstream.video.android.core.internal.InternalStreamVideoApi
import io.getstream.video.android.core.notifications.internal.service.permissions.AudioCallPermissionManager
import io.getstream.video.android.core.notifications.internal.service.permissions.ForegroundServicePermissionManager
import kotlin.jvm.java

@InternalStreamVideoApi
public object StreamForegroundPermissionUtil {

public fun getForegroundPermissionsForCallType(callType: String): Set<Int> {
val client = StreamVideo.instanceOrNull() as? StreamVideoClient
if (client != null) {
val config = client.callServiceConfigRegistry.get(callType)
return when (config.serviceClass) {
CallService::class.java -> ForegroundServicePermissionManager().requiredForegroundTypes
AudioCallService::class.java -> AudioCallPermissionManager().requiredForegroundTypes
else -> emptySet()
}
}
Comment thread
aleksandar-apostolov marked this conversation as resolved.
return emptySet()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ internal class CallServiceNotificationUpdateObserver(

when (ringingState) {
is RingingState.Active -> {
showActiveCallNotification(context, callId, notification)
showActiveCallNotification(callId, notification)
}
is RingingState.Outgoing -> {
showOutgoingCallNotification(context, callId, notification)
Expand All @@ -133,19 +133,20 @@ internal class CallServiceNotificationUpdateObserver(
}

private fun showActiveCallNotification(
context: Context,
callId: StreamCallId,
notification: Notification,
) {
logger.d { "[showActiveCallNotification] Showing active call notification" }
val notificationId =
call.state.notificationIdFlow.value ?: callId.getNotificationId(NotificationType.Ongoing)
startForegroundWithServiceType(
notificationId,
notification,
CallService.Companion.TRIGGER_ONGOING_CALL,
permissionManager.getServiceType(context, CallService.Companion.TRIGGER_ONGOING_CALL),
)

streamVideo
.getStreamNotificationDispatcher()
.notify(
callId,
notificationId,
notification,
)
}

private fun showOutgoingCallNotification(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ internal open class ForegroundServicePermissionManager {

fun getServiceType(context: Context, trigger: String): Int {
return when (trigger) {
CallService.Companion.TRIGGER_ONGOING_CALL -> calculateServiceType(context)
CallService.Companion.TRIGGER_ONGOING_CALL,
CallService.Companion.TRIGGER_OUTGOING_CALL,
-> calculateServiceType(context)
else -> noPermissionServiceType()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import io.getstream.video.android.core.ParticipantState
import io.getstream.video.android.core.RingingState
import io.getstream.video.android.core.StreamVideoClient
import io.getstream.video.android.core.notifications.NotificationType
import io.getstream.video.android.core.notifications.dispatchers.DefaultNotificationDispatcher
import io.getstream.video.android.core.notifications.internal.service.CallService
import io.getstream.video.android.core.notifications.internal.service.permissions.ForegroundServicePermissionManager
import io.getstream.video.android.model.StreamCallId
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -135,7 +137,6 @@ class CallServiceNotificationUpdateObserverTest {
@Test
fun `incoming ringing state starts incoming foreground notification`() = runTest {
observer.observe(context)
// advanceUntilIdle()

ringingStateFlow.value = RingingState.Incoming()
advanceUntilIdle()
Expand Down Expand Up @@ -171,21 +172,31 @@ class CallServiceNotificationUpdateObserverTest {
}

@Test
fun `active ringing state starts ongoing foreground notification`() = runTest {
fun `active ringing state dispatches ongoing call notification`() = runTest {
val notificationDispatcher = mockk<DefaultNotificationDispatcher>(relaxed = true)
val mockNotification = mockk<Notification>()

every { streamVideo.getStreamNotificationDispatcher() } returns notificationDispatcher
coEvery { streamVideo.onCallNotificationUpdate(call) } returns mockNotification

observer.observe(context)

advanceUntilIdle()

ringingStateFlow.value = RingingState.Active

advanceUntilIdle()
advanceTimeBy(100L)
advanceTimeBy(100)

val args = startArgs!!
assertEquals(
StreamCallId("default", "call-1")
.getNotificationId(NotificationType.Ongoing),
args.first,
)
assertEquals(CallService.TRIGGER_ONGOING_CALL, args.third)
val streamCallId = StreamCallId("default", "call-1")

verify {
notificationDispatcher.notify(
streamCallId,
streamCallId.getNotificationId(NotificationType.Ongoing),
mockNotification,
)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,42 @@
.systemBarsPadding(),
) {
logger.d { "[setContent] with RootContent" }
activity.RootContent(call = call)

val renderPermissionUi by activity.renderPermissionUi.collectAsStateWithLifecycle()
if (renderPermissionUi) {
Comment thread
aleksandar-apostolov marked this conversation as resolved.
NoPermissionUi(activity, call) {
activity.updateRenderPermissionUi(false)
}
} else {
activity.RootContent(call = call)
}
}
}
}
}
}

@Composable
private fun NoPermissionUi(
activity: StreamCallActivity,
call: Call,
onAllPermissionGranted: () -> Unit,
) {
LaunchPermissionRequest(getRequiredPermissions(call)) {
AllPermissionsGranted {
onAllPermissionGranted()
}

SomeGranted { granted, notGranted, showRationale ->
activity.InternalPermissionContent(showRationale, call, granted, notGranted)
}

NoneGranted {
activity.InternalPermissionContent(it, call, emptyList(), emptyList())
}
}
}

@StreamCallActivityDelicateApi
@Composable
override fun StreamCallActivity.RootContent(call: Call) {
Expand Down Expand Up @@ -296,7 +325,7 @@
},
onCallAction = {
onCallAction(call, it)
callAction = it

Check warning on line 328 in stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

The value assigned here is never used.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZzSUoLTuIJseWZVebkG&open=AZzSUoLTuIJseWZVebkG&pullRequest=1627
},
onIdle = {
LoadingContent(call)
Expand Down Expand Up @@ -369,6 +398,7 @@
) {
if (!showRationale && configurationMap[call.id]?.canSkipPermissionRationale == true) {
logger.w { "Permissions were not granted, but rationale is required to be skipped." }
updateRenderPermissionUi(false)
safeFinish()
} else {
PermissionsRationaleContent(call, granted, notGranted)
Expand Down Expand Up @@ -599,6 +629,7 @@
ButtonStyles.tertiaryButtonStyle(StyleSize.S),
) {
// No permissions, leave the call
updateRenderPermissionUi(false)
onCallAction(call, LeaveCall)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,6 @@ public final class io/getstream/video/android/ui/common/util/ColorUtilsKt {

public final class io/getstream/video/android/ui/common/util/ParticipantsTextKt {
public static final fun buildLargeCallText (Landroid/content/Context;Ljava/util/List;)Ljava/lang/String;
public static final fun buildSmallCallText (Landroid/content/Context;Ljava/util/List;IZ)Ljava/lang/String;
public static synthetic fun buildSmallCallText$default (Landroid/content/Context;Ljava/util/List;IZILjava/lang/Object;)Ljava/lang/String;
}

public final class io/getstream/video/android/ui/common/util/ResourcesKt {
Expand Down
3 changes: 3 additions & 0 deletions stream-video-android-ui-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
plugins {
id("io.getstream.video.android.library")
}
apiValidation {
nonPublicMarkers.add("io.getstream.video.android.core.internal.InternalStreamVideoApi")
}

android {
namespace = "io.getstream.video.android.ui.common"
Expand Down
Loading
Loading