Skip to content

fix(voip): defer foreground service start until call is active on Android#7283

Merged
diegolmello merged 1 commit into
feat.voip-lib-newfrom
fix/voip-pr6918-02-accept-flow-fgs-ordering
Apr 30, 2026
Merged

fix(voip): defer foreground service start until call is active on Android#7283
diegolmello merged 1 commit into
feat.voip-lib-newfrom
fix/voip-pr6918-02-accept-flow-fgs-ordering

Conversation

@diegolmello

@diegolmello diegolmello commented Apr 30, 2026

Copy link
Copy Markdown
Member

Proposed changes

On Android 14, FOREGROUND_SERVICE_TYPE_PHONE_CALL may only be started after the Telecom connection has transitioned to the active state. The previous code started VoipCallService immediately when the user tapped Accept — before the server answer REST call completed and before connection.onAnswer() was called — causing ForegroundServiceStartNotAllowedException on Android 14 and silently killing the call.

A second issue: VoipCallService.onStartCommand only called startForeground inside the ACTION_START branch. The ACTION_STOP and unknown-action branches called stopSelf without entering foreground state, so the OS five-second rule fired ForegroundServiceDidNotStartInTimeException on sticky restarts and unexpected intent redelivery.

Issue(s)

Part of PR #6918 ship-blocking fixes — blockers B2 and B3.

How to test or reproduce

Happy path (B2):

  1. On Android 14, receive an incoming VoIP call.
  2. Tap Accept.
  3. Verify the foreground notification appears within ~200–800 ms after the tap (after the server answer completes), audio connects, and no ForegroundServiceStartNotAllowedException appears in Logcat.

Failure path (B2):

  1. Simulate a server answer failure (e.g. network off or mock the REST endpoint to return an error).
  2. Tap Accept.
  3. Verify no foreground service notification is started and the existing failure tear-down (broadcast, notification cancel) runs — no stranded notification.

Unknown-action / sticky restart (B3):

  1. Force-stop and relaunch the app while VoipCallService was running.
  2. Verify no ForegroundServiceDidNotStartInTimeException in Logcat.

Screenshots

N/A — notification behaviour change only.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

B2 — accept flow ordering: VoipCallService.startService is now called inside the finish(true) branch of handleAcceptAction, immediately after answerIncomingCall() transitions the Telecom connection to active. If the REST answer fails or times out, the service is never started and the existing tear-down path runs unchanged.

B3 — foreground-first command handler: onStartCommand now calls startForegroundWithNotification unconditionally at the top of the method before the when branch. The ACTION_STOP and unknown-action branches still call stopSelf; the brief foreground promotion followed by stopSelf is the correct pattern Android documents for this scenario.

Accept-tap-to-foreground-notification latency: ~200–800 ms on a healthy connection (dominated by the server round-trip for the media-calls.answer REST call). This is within the target range specified in the acceptance criteria.

Summary by CodeRabbit

Bug Fixes

  • Enhanced VoIP service initialization with immediate foreground handling for improved reliability
  • Improved call acceptance flow to ensure proper synchronization with connection establishment
  • Refined service lifecycle management for more consistent call operations

…roid

On Android 14, starting FOREGROUND_SERVICE_TYPE_PHONE_CALL before the
Telecom connection is active throws ForegroundServiceStartNotAllowedException
and kills the call. Move the startService call to after answerIncomingCall()
succeeds. Also harden VoipCallService.onStartCommand to enter foreground
state before branching on intent action, preventing ForegroundServiceDidNotStartInTimeException
on sticky restarts and unexpected intent redelivery.
@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The changes modify the timing of VoIP service foreground initialization. VoipCallService now transitions to foreground immediately on command execution, while VoipNotification defers service startup until after the REST call succeeds and the Telecom connection is marked active.

Changes

Cohort / File(s) Summary
VoIP Service Initialization
android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt, android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt
Modified foreground service startup sequence: VoipCallService now enters foreground immediately on onStartCommand execution by extracting callId and invoking startForegroundWithNotification() before action checking, eliminating duplicate startup logic in ACTION_START path. VoipNotification defers service startup until after REST call succeeds and Telecom connection activation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: deferring foreground service start until the call is active on Android, which directly addresses the ForegroundServiceStartNotAllowedException blocker.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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
Review rate limit: 4/8 reviews remaining, refill in 26 minutes and 18 seconds.

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

@diegolmello diegolmello temporarily deployed to experimental_android_build April 30, 2026 18:23 — with GitHub Actions Inactive
@diegolmello diegolmello had a problem deploying to experimental_ios_build April 30, 2026 18:23 — with GitHub Actions Failure
@diegolmello diegolmello had a problem deploying to official_android_build April 30, 2026 18:23 — with GitHub Actions Failure

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt (1)

274-303: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Guard the delayed service start against teardown races.

finish(true) always starts VoipCallService, but answerIncomingCall(...) does not prove the call is still live. If the DDP hangup/other-device path tears the call down before this callback runs, the later success path can still resurrect the foreground service for an already-ended call.

Please gate the service start on a real post-answer liveness result, or thread a success/failure signal back from answerIncomingCall(...) so teardown can win cleanly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt`
around lines 274 - 303, The finish(...) path currently unconditionally calls
VoipCallService.startService after answerIncomingCall, which can resurrect a
service for a call already torn down; modify the flow so starting the foreground
service is gated by a real post-answer liveness check: either make
answerIncomingCall(payload.callId) return a boolean/Result indicating the call
is actually connected (or throw) and only call VoipCallService.startService when
that result is success, or call a new ddpRegistry/isCallActive(payload.callId)
(or similar) immediately after answerIncomingCall and only start the service if
that check returns true; ensure the failure branch still runs
disconnectIncomingCall and storeAcceptFailureForJs when liveness is false so
teardown wins cleanly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt`:
- Around line 274-303: The finish(...) path currently unconditionally calls
VoipCallService.startService after answerIncomingCall, which can resurrect a
service for a call already torn down; modify the flow so starting the foreground
service is gated by a real post-answer liveness check: either make
answerIncomingCall(payload.callId) return a boolean/Result indicating the call
is actually connected (or throw) and only call VoipCallService.startService when
that result is success, or call a new ddpRegistry/isCallActive(payload.callId)
(or similar) immediately after answerIncomingCall and only start the service if
that check returns true; ensure the failure branch still runs
disconnectIncomingCall and storeAcceptFailureForJs when liveness is false so
teardown wins cleanly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c5eb3f0-a1c0-4f53-83c7-432a935c6ceb

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea0877 and 5be4d74.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP features in app/lib/services/voip/ directory using Zustand stores for WebRTC peer-to-peer audio calls with native CallKit (iOS) and Telecom (Android) integration
📚 Learning: 2026-04-22T22:57:58.545Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-22T22:57:58.545Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP features in app/lib/services/voip/ directory using Zustand stores for WebRTC peer-to-peer audio calls with native CallKit (iOS) and Telecom (Android) integration

Applied to files:

  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt
🔇 Additional comments (2)
android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt (1)

66-95: Looks good.

Moving startForegroundWithNotification(...) ahead of the action branch addresses the Android 14 timing issue without changing the stop paths’ behavior.

android/app/src/main/java/chat/rocket/reactnative/voip/VoipNotification.kt (1)

262-267: Good deferral point.

Starting the foreground service here would reintroduce the Android 14 ordering problem, so keeping it out of this pre-finish block is the right move.

@diegolmello diegolmello had a problem deploying to upload_experimental_android April 30, 2026 19:24 — with GitHub Actions Failure
@github-actions

Copy link
Copy Markdown

Android Build Available

Rocket.Chat Experimental 4.72.0.108720

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNTaxDmMOsZz3MPi3o7O8gBbojvORr88KSCRhltnLwFdC37GKsi4AEKuiUe5Zk7loI3wQSmCqt9LsXkMJwAv

@diegolmello diegolmello merged commit 891ee58 into feat.voip-lib-new Apr 30, 2026
8 of 13 checks passed
@diegolmello diegolmello deleted the fix/voip-pr6918-02-accept-flow-fgs-ordering branch April 30, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant