Skip to content

Improve Foreground Service orchestration when transitioning from outgoing call to active call#1627

Open
rahul-lohra wants to merge 7 commits intodevelopfrom
bugfix/rahullohra/outgoing-call-fg-service-exception
Open

Improve Foreground Service orchestration when transitioning from outgoing call to active call#1627
rahul-lohra wants to merge 7 commits intodevelopfrom
bugfix/rahullohra/outgoing-call-fg-service-exception

Conversation

@rahul-lohra
Copy link
Contributor

@rahul-lohra rahul-lohra commented Mar 9, 2026

Goal

  1. Transition from Outgoing call to active call should not crash when launching FG Service
  2. Render no permission when permissions are missing in making outgoing call

Implementation

  1. Transition from Outgoing call to active call should not crash when launching FG Service

Root Cause - Launching FG Service even when the application is in background and ringing state transitions from outgoing to active

Proposed Solution

  1. Launch only FG Serive once with trigger type as outgoing-call

Because of this we need to correctly handle the following
2. Correctly update the notification transition from outgoing to active

🎨 UI Changes

Just a no permission ui in outgoing-call flow

Before After
img img

Add relevant videos

Testing

Scenario: Outgoing call → background → call accepted
1. Make an outgoing call to another user.
2. While the call is still in the outgoing state, press the Home button to send the app to the background.
3. Accept the call on the other device.

Expected behavior
• The caller should transition smoothly from outgoing → active state.
• PiP should not be rendered, since it was never shown before the app went to the background.
• The Call Service should continue running without interruption.
• The notification should update from outgoing → active.

End condition
• If either the caller or callee declines/ends the call, the other participant should also leave the call and the session should terminate correctly.

Summary by CodeRabbit

  • New Features

    • Added a permission request UI for foreground service permissions during call setup.
    • Enhanced capability management to properly reflect available permissions for outgoing calls.
  • Bug Fixes

    • Improved foreground service handling to prevent unnecessary service starts and log errors when required services are unavailable.

… if permissions are missing when making outgoing call
@rahul-lohra rahul-lohra self-assigned this Mar 9, 2026
@rahul-lohra rahul-lohra added the pr:bug Fixes a bug label Mar 9, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-video-android-core 12.00 MB 12.00 MB 0.00 MB 🟢
stream-video-android-ui-xml 5.68 MB 5.68 MB 0.00 MB 🟢
stream-video-android-ui-compose 6.27 MB 6.27 MB 0.00 MB 🟢

@rahul-lohra rahul-lohra marked this pull request as ready for review March 9, 2026 13:03
@rahul-lohra rahul-lohra requested a review from a team as a code owner March 9, 2026 13:03
@rahul-lohra rahul-lohra changed the title Improve Foreground Service orchestration when transitioning from outgoing call to active call [AND-1100] Improve Foreground Service orchestration when transitioning from outgoing call to active call Mar 9, 2026
@rahul-lohra rahul-lohra changed the title [AND-1100] Improve Foreground Service orchestration when transitioning from outgoing call to active call Improve Foreground Service orchestration when transitioning from outgoing call to active call Mar 9, 2026
@rahul-lohra rahul-lohra changed the title Improve Foreground Service orchestration when transitioning from outgoing call to active call [AND-1079] Improve Foreground Service orchestration when transitioning from outgoing call to active call Mar 9, 2026
@rahul-lohra rahul-lohra changed the title [AND-1079] Improve Foreground Service orchestration when transitioning from outgoing call to active call Improve Foreground Service orchestration when transitioning from outgoing call to active call Mar 9, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

Walkthrough

The changes implement a permission-based UI flow for foreground services in video calls. A new permission handling system checks required permissions before call initialization, shows conditional UI for permission requests, and updates service startup logic to verify service running state rather than automatically starting foreground services. Capabilities are now set based on available foreground permissions.

Changes

Cohort / File(s) Summary
Permission UI Flow
stream-video-android-ui-compose/.../StreamCallActivityComposeDelegate.kt, stream-video-android-ui-core/src/main/kotlin/.../StreamCallActivity.kt, stream-video-android-ui-core/api/stream-video-android-ui-core.api
Introduces conditional permission UI rendering in compose delegate with NoPermissionUi composable. Adds StateFlow-based permission UI state management and methods (getRenderPermissionUi, setRenderPermissionUi, updateRenderPermissionUi) to StreamCallActivity. Capability setup now occurs before call creation with permission-aware fallbacks.
Foreground Service Permission Utilities
stream-video-android-core/.../StreamForegroundPermissionUtil.kt
New utility object providing getForegroundPermissionsForCallType() to determine required foreground permission types based on service configuration (CallService vs AudioCallService).
Service Startup and Capability Management
stream-video-android-core/.../ClientState.kt, stream-video-android-core/.../CallState.kt
Replaces automatic foreground service startup with runtime service status checks using ServiceIntentBuilder. Logs error if service not running when runCallServiceInForeground is enabled. Adds setOwnCapabilities() method to CallState for capability updates.
Notification and Permission Routing
stream-video-android-core/.../CallServiceNotificationUpdateObserver.kt, stream-video-android-core/.../ForegroundServicePermissionManager.kt
Changes active call notifications to delegate through StreamVideo's notification dispatcher instead of direct foreground service start. Expands service type mapping to include TRIGGER_OUTGOING_CALL routing.
Demo App Configuration
demo-app/.../StreamVideoInitHelper.kt
Adds getOngoingCallBundle() override in DefaultNotificationIntentBundleResolver with closeScreenOnCallEnded and leaveWhenLastInCall flags.

Sequence Diagram

sequenceDiagram
    participant User
    participant StreamCallActivity
    participant PermissionUI as Permission UI Flow
    participant PermissionManager
    participant ServiceChecker
    participant Dispatcher as Notification Dispatcher
    
    User->>StreamCallActivity: Start Call
    StreamCallActivity->>PermissionManager: Check required foreground permissions
    
    alt Permissions Missing
        PermissionManager-->>StreamCallActivity: Missing permissions
        StreamCallActivity->>PermissionUI: Show permission request UI
        PermissionUI->>User: Request foreground permissions
        User->>PermissionUI: Grant/Deny permissions
        PermissionUI->>StreamCallActivity: Permissions result
        StreamCallActivity->>StreamCallActivity: Update capabilities based on granted permissions
    else All Permissions Available
        PermissionManager-->>StreamCallActivity: All permissions available
        StreamCallActivity->>StreamCallActivity: Set full capabilities
    end
    
    StreamCallActivity->>ServiceChecker: Verify call service is running
    
    alt Service Running
        ServiceChecker-->>StreamCallActivity: Service confirmed running
    else Service Not Running
        ServiceChecker-->>StreamCallActivity: Service not running (log error)
    end
    
    StreamCallActivity->>Dispatcher: Dispatch call notification
    Dispatcher->>Dispatcher: Handle notification with foreground permissions
    Dispatcher-->>User: Display call UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Whiskers twitch with permission delight,
Service checks now running just right,
Foreground flows with UI so clean,
Capabilities dancing, permissions seen!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description includes all key required sections: Goal (addresses both root causes), Implementation (explains proposed solutions), UI Changes, Testing scenario with expected behavior, and aligns with the changesets across multiple files.
Title check ✅ Passed The title accurately summarizes the main objective of the PR: improving foreground service orchestration during the transition from outgoing to active calls, which is the core focus across all file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/rahullohra/outgoing-call-fg-service-exception

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt (1)

1784-1787: Parameter name should be plural to match type.

The parameter ownCapability is singular but expects a List<OwnCapability>. Use ownCapabilities for consistency with the internal _ownCapabilities field.

✨ Proposed fix
     `@InternalStreamVideoApi`
-    fun setOwnCapabilities(ownCapability: List<OwnCapability>) {
-        this._ownCapabilities.value = ownCapability
+    fun setOwnCapabilities(ownCapabilities: List<OwnCapability>) {
+        this._ownCapabilities.value = ownCapabilities
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt`
around lines 1784 - 1787, The parameter name in setOwnCapabilities should be
plural to match its type and the internal field; rename the parameter
ownCapability to ownCapabilities in the function signature of setOwnCapabilities
and update all usages inside the function (this._ownCapabilities.value) and any
call sites referencing that parameter to use the new name; ensure
imports/overloads are unchanged and rebuild to catch any unresolved references.
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StreamForegroundPermissionUtil.kt (2)

24-24: Remove unnecessary import.

The import kotlin.jvm.java is not needed. The .java extension property for getting a Java Class from a Kotlin KClass is available without explicit import.

✨ Proposed fix
 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
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StreamForegroundPermissionUtil.kt`
at line 24, The file StreamForegroundPermissionUtil.kt contains an unnecessary
import `kotlin.jvm.java`; remove that import line so the code relies on the
built-in `.java` extension on KClass (no other code changes required), e.g.,
delete the `import kotlin.jvm.java` entry in StreamForegroundPermissionUtil.kt
to clean up unused imports.

29-40: Consider caching permission managers and documenting the sentinel value.

  1. ForegroundServicePermissionManager() and AudioCallPermissionManager() are instantiated on every call. Consider caching them since requiredForegroundTypes is a constant set.

  2. The return value setOf(-1) is a sentinel value that should be documented for callers.

♻️ Proposed improvement
 `@InternalStreamVideoApi`
 public object StreamForegroundPermissionUtil {

+    private val callServicePermissionManager = ForegroundServicePermissionManager()
+    private val audioCallPermissionManager = AudioCallPermissionManager()
+
+    /**
+     * Returns the required foreground service types for the given call type.
+     * `@param` callType The call type string.
+     * `@return` Set of ServiceInfo.FOREGROUND_SERVICE_TYPE_* constants, or setOf(-1) if
+     *         StreamVideo client is not initialized or an unknown service class is configured.
+     */
     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
+                CallService::class.java -> callServicePermissionManager.requiredForegroundTypes
+                AudioCallService::class.java -> audioCallPermissionManager.requiredForegroundTypes
                 else -> setOf(-1)
             }
         }
         return setOf(-1)
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StreamForegroundPermissionUtil.kt`
around lines 29 - 40, getForegroundPermissionsForCallType currently instantiates
ForegroundServicePermissionManager and AudioCallPermissionManager on every call
and returns a magic sentinel setOf(-1) undocumented; fix by adding cached
instances (e.g., private val foregroundPermissionManager and
audioCallPermissionManager) and reuse their requiredForegroundTypes in
getForegroundPermissionsForCallType, and replace the inline sentinel with a
named constant (e.g., UNKNOWN_FOREGROUND_PERMISSION = setOf(-1)) documented via
KDoc on the function or constant to explain the sentinel meaning for callers.
stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt (1)

127-135: Consider guarding against redundant recomposition.

When AllPermissionsGranted fires, onAllPermissionGranted() sets renderPermissionUi to false. However, since AllPermissionsGranted fires on every recomposition while permissions are granted (see context snippet 3), there's a brief window where the callback may execute multiple times before the state change propagates.

While this is harmless (setting false to false is idempotent), you could add a guard for clarity.

✨ Optional guard
-                        if (renderPermissionUi) {
+                        val renderPermissionUi by activity.renderPermissionUi.collectAsStateWithLifecycle()
+                        if (renderPermissionUi) {
                             NoPermissionUi(activity, call) {
-                                activity.updateRenderPermissionUi(false)
+                                if (activity.renderPermissionUi.value) {
+                                    activity.updateRenderPermissionUi(false)
+                                }
                             }
                         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt`
around lines 127 - 135, The NoPermissionUi callback can trigger redundant
updates because AllPermissionsGranted may fire multiple times before state
updates; modify the callback in StreamCallActivityComposeDelegate to check the
current renderPermissionUi state (from
activity.renderPermissionUi.collectAsStateWithLifecycle()/renderPermissionUi) or
query activity before calling activity.updateRenderPermissionUi(false) so you
only call updateRenderPermissionUi(false) when renderPermissionUi is true, e.g.,
add a guard around the lambda passed to NoPermissionUi that references
renderPermissionUi (or calls a new activity.isRenderPermissionUiVisible()) to
avoid redundant state writes from onAllPermissionGranted.
stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/observers/CallServiceNotificationUpdateObserver.kt (1)

144-151: Verify notification dispatcher fallback when service is not running.

The change delegates active call notification to StreamVideo.instanceOrNull()?.getStreamNotificationDispatcher()?.notify(), which only posts a notification without starting the foreground service (as confirmed by DefaultNotificationDispatcher.notify() at lines 27-40). This is correct for the outgoing→active transition where the service should already be running.

However, if StreamVideo.instanceOrNull() returns null or the dispatcher is unavailable, this silently fails without logging or fallback.

♻️ Proposed improvement with fallback logging
     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)

-        StreamVideo.instanceOrNull()
-            ?.getStreamNotificationDispatcher()
-            ?.notify(
-                callId,
-                notificationId,
-                notification,
-            )
+        val dispatcher = StreamVideo.instanceOrNull()?.getStreamNotificationDispatcher()
+        if (dispatcher != null) {
+            dispatcher.notify(callId, notificationId, notification)
+        } else {
+            logger.w { "[showActiveCallNotification] StreamVideo dispatcher unavailable, notification not posted" }
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/observers/CallServiceNotificationUpdateObserver.kt`
around lines 144 - 151, CallServiceNotificationUpdateObserver currently forwards
active-call notifications via
StreamVideo.instanceOrNull()?.getStreamNotificationDispatcher()?.notify(...),
which silently does nothing when StreamVideo or the dispatcher is null; update
the observer to handle that fallback by checking for null and either logging a
clear warning/error (including callId/notificationId) and/or starting the
foreground call service directly when the dispatcher is unavailable; reference
StreamVideo.instanceOrNull(), getStreamNotificationDispatcher(), notify(), and
DefaultNotificationDispatcher.notify() when making the change so the fix ensures
the active call is surfaced or at least diagnostic logs are emitted when the
dispatcher/service isn't present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt`:
- Around line 188-198: The check uses reflection incorrectly:
callServiceConfig.serviceClass is already a Class<out Service>, so replace the
redundant serviceClass::class.java usage in the
ServiceIntentBuilder().isServiceRunning call with the serviceClass value
directly; update the invocation in the ClientState code that uses
callServiceConfig and ServiceIntentBuilder.isServiceRunning (passing
this.client.context and serviceClass) to avoid taking the KClass of the Class
object.

In
`@stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt`:
- Around line 875-880: The call to uiDelegate.setContent(...) and any UI state
reads/updates (_renderPermissionUi.value, renderPermissionUi.first {...}) are
being executed on Dispatchers.IO; move those UI interactions onto the Main
dispatcher (e.g., with withContext(Dispatchers.Main) or
lifecycleScope.launch(Dispatchers.Main)) before calling create(...). Also
prevent infinite recursion by adding a retry guard: introduce a permission retry
counter (e.g., permissionRetryCount field and MAX_PERMISSION_RETRIES constant)
and increment it each time create(...) is re-invoked from the permission flow
(referencing create and renderPermissionUi); when the max is exceeded, perform a
graceful fallback such as finishing the activity with an error instead of
recursing.
- Around line 884-904: The current setCallCapabilitiesIfNotPresent overwrites
existing capabilities with an empty list because
call.state.setOwnCapabilities(outgoingCallCapabilities) is executed even for
non-outgoing intents or when no permissions match; update the logic in
setCallCapabilitiesIfNotPresent so that you only compute and set capabilities
when intent.action == NotificationHandler.ACTION_OUTGOING_CALL and only call
call.state.setOwnCapabilities(...) when outgoingCallCapabilities is non-empty
(or at least inside that outgoing-call branch), using
StreamForegroundPermissionUtil.getForegroundPermissionsForCallType to populate
outgoingCallCapabilities and keeping the early return that checks
call.state.ownCapabilities.value.

---

Nitpick comments:
In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt`:
- Around line 1784-1787: The parameter name in setOwnCapabilities should be
plural to match its type and the internal field; rename the parameter
ownCapability to ownCapabilities in the function signature of setOwnCapabilities
and update all usages inside the function (this._ownCapabilities.value) and any
call sites referencing that parameter to use the new name; ensure
imports/overloads are unchanged and rebuild to catch any unresolved references.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/observers/CallServiceNotificationUpdateObserver.kt`:
- Around line 144-151: CallServiceNotificationUpdateObserver currently forwards
active-call notifications via
StreamVideo.instanceOrNull()?.getStreamNotificationDispatcher()?.notify(...),
which silently does nothing when StreamVideo or the dispatcher is null; update
the observer to handle that fallback by checking for null and either logging a
clear warning/error (including callId/notificationId) and/or starting the
foreground call service directly when the dispatcher is unavailable; reference
StreamVideo.instanceOrNull(), getStreamNotificationDispatcher(), notify(), and
DefaultNotificationDispatcher.notify() when making the change so the fix ensures
the active call is surfaced or at least diagnostic logs are emitted when the
dispatcher/service isn't present.

In
`@stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StreamForegroundPermissionUtil.kt`:
- Line 24: The file StreamForegroundPermissionUtil.kt contains an unnecessary
import `kotlin.jvm.java`; remove that import line so the code relies on the
built-in `.java` extension on KClass (no other code changes required), e.g.,
delete the `import kotlin.jvm.java` entry in StreamForegroundPermissionUtil.kt
to clean up unused imports.
- Around line 29-40: getForegroundPermissionsForCallType currently instantiates
ForegroundServicePermissionManager and AudioCallPermissionManager on every call
and returns a magic sentinel setOf(-1) undocumented; fix by adding cached
instances (e.g., private val foregroundPermissionManager and
audioCallPermissionManager) and reuse their requiredForegroundTypes in
getForegroundPermissionsForCallType, and replace the inline sentinel with a
named constant (e.g., UNKNOWN_FOREGROUND_PERMISSION = setOf(-1)) documented via
KDoc on the function or constant to explain the sentinel meaning for callers.

In
`@stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt`:
- Around line 127-135: The NoPermissionUi callback can trigger redundant updates
because AllPermissionsGranted may fire multiple times before state updates;
modify the callback in StreamCallActivityComposeDelegate to check the current
renderPermissionUi state (from
activity.renderPermissionUi.collectAsStateWithLifecycle()/renderPermissionUi) or
query activity before calling activity.updateRenderPermissionUi(false) so you
only call updateRenderPermissionUi(false) when renderPermissionUi is true, e.g.,
add a guard around the lambda passed to NoPermissionUi that references
renderPermissionUi (or calls a new activity.isRenderPermissionUiVisible()) to
avoid redundant state writes from onAllPermissionGranted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1f94450e-0056-4b12-b8f3-755fd3801401

📥 Commits

Reviewing files that changed from the base of the PR and between 94feaea and 496b87b.

📒 Files selected for processing (9)
  • demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/StreamForegroundPermissionUtil.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/observers/CallServiceNotificationUpdateObserver.kt
  • stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/permissions/ForegroundServicePermissionManager.kt
  • stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/StreamCallActivityComposeDelegate.kt
  • stream-video-android-ui-core/api/stream-video-android-ui-core.api
  • stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt

Comment on lines +138 to +139
public final fun setRenderPermissionUi (Lkotlinx/coroutines/flow/StateFlow;)V
public final fun updateRenderPermissionUi (Z)V
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be here. I already marked these variables with @InternalStreamVideoApi @aleksandar-apostolov @gpunto

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they are public, they are still part of the public api, the annotation just forces consumers to opt in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I understand that if something is public it is technically part of the public API, and @InternalStreamVideoApi only forces consumers to opt-in.

What I’m observing is that the API validator behaves differently across modules.

For example, when a public API is annotated with @InternalStreamVideoApi:
In stream-video-android-core, the API does not appear in the .api file, which is the behavior I expected.
• In stream-video-android-ui-compose, the API **does get added to the .api file`.

For reference, this public API added in the core module in the same PR did not update the .api file:
https://github.com/GetStream/stream-video-android/pull/1627/changes#diff-f229deea2aae6b806a751cc451836b9720165dd57e4de173cc40c2fd43169965R27-R29

My guess is that the apiValidation configuration is not applied consistently across modules.

It is currently configured in stream-video-android-core:
stream-video-android-core/build.gradle

But it is missing in stream-video-android-ui-compose:
stream-video-android-ui-compose/build.gradle

To make the behavior consistent, we could define the marker once in a shared configuration so all stream-* modules inherit the same rule:

apiValidation {
    nonPublicMarkers += listOf(
        "io.getstream.video.android.core.internal.InternalStreamVideoApi"
    )
}

This way any public API annotated with @InternalStreamVideoApi would be treated as non-public by the validator across all modules and we avoid these inconsistencies.

For this PR, I’ll add the apiValidation configuration to stream-video-android-ui-compose to make the behavior consistent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, when a public API is annotated with @InternalStreamVideoApi:
• In stream-video-android-core, the API does not appear in the .api file, which is the behavior I expected.
• In stream-video-android-ui-compose, the API **does get added to the .api file`.

Good point, I didn't know that!

My guess is that the apiValidation configuration is not applied consistently across modules.

That definitely seems to be the case so it makes sense to also add the same config to the module 👍

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
18.2% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

withContext(Dispatchers.Main) {
uiDelegate.setContent(activity = this@StreamCallActivity, call)
}
renderPermissionUi.first { !it }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user denies permissions, NoPermissionUi calls InternalPermissionContent for SomeGranted/NoneGranted but never calls onAllPermissionGranted() — wouldn't _renderPermissionUi never flip to false, causing .first { \!it } to suspend forever? Dead coroutine on IO, no error callback, no way out for the user. Should there be a cancel/deny path that sets _renderPermissionUi = false and calls onError?

activity.RootContent(call = call)

val renderPermissionUi by activity.renderPermissionUi.collectAsStateWithLifecycle()
if (renderPermissionUi) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to the above — NoPermissionUi only provides onAllPermissionGranted as a dismiss path. How do SomeGranted and NoneGranted signal failure back to the create() coroutine?

AudioCallService::class.java -> AudioCallPermissionManager().requiredForegroundTypes
else -> setOf(-1)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setOf(-1) as a sentinel for "no permissions" is fragile — if anything downstream iterates expecting valid ServiceInfo.FOREGROUND_SERVICE_TYPE_* constants, -1 could cause unexpected behavior. emptySet() would be clearer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:bug Fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants