Skip to content

fix(android): remove isRunning guard + add double-tap guard on Accept/Decline#7217

Merged
diegolmello merged 1 commit intofeat.voip-lib-newfrom
fix/c3-c4-android-voiplib
Apr 23, 2026
Merged

fix(android): remove isRunning guard + add double-tap guard on Accept/Decline#7217
diegolmello merged 1 commit intofeat.voip-lib-newfrom
fix/c3-c4-android-voiplib

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented Apr 22, 2026

Summary

  • C3: Remove isRunning boolean guard so startForegroundWithNotification() is called on every ACTION_START (Android 14+ foreground service compliance)
  • C4: Add AtomicBoolean guard on handleAccept/handleDecline to prevent double-tap from triggering multiple service starts

Test plan

  • Kotlin syntax check passes
  • Android build succeeds

Closes #6918

Summary by CodeRabbit

  • Bug Fixes
    • Prevented duplicate execution of call accept/decline actions to improve reliability and avoid repeated side effects.
    • Ensured the VoIP service consistently marks itself running and presents the foreground notification on each start to improve notification and lifecycle behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Walkthrough

Incoming call handling and the VoIP foreground service startup were tightened: an AtomicBoolean guard prevents duplicate accept/decline processing in IncomingCallActivity, and VoipCallService now always marks itself running and calls startForegroundWithNotification() on every ACTION_START intent.

Changes

Cohort / File(s) Summary
Incoming call UI / actions
android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt
Added a shared AtomicBoolean (acceptDeclineGuard) and updated handleAccept/handleDecline to use compareAndSet(false, true) so only the first action proceeds; subsequent calls return early, preventing duplicated timeout cancellation, notification cleanup, action handling, and activity finish.
VoIP foreground service startup
android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt
Removed the conditional skip for duplicate starts in the ACTION_START branch; now isRunning is set to true and startForegroundWithNotification(callId) is invoked unconditionally for each start intent.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 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 0.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 describes the two main changes: removing the isRunning guard and adding a double-tap guard, both of which are reflected in the code modifications.
Linked Issues check ✅ Passed The PR addresses Android VoIP compliance by ensuring proper foreground service notification lifecycle and preventing duplicate action triggers through a double-tap guard.
Out of Scope Changes check ✅ Passed All changes are directly scoped to Android VoIP call handling improvements: foreground service compliance and preventing duplicate accept/decline actions.

✏️ 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

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

Copy link
Copy Markdown
Contributor

@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.

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/IncomingCallActivity.kt (1)

262-280: ⚠️ Potential issue | 🔴 Critical

Invert the guard condition so the first tap is not swallowed.

Lines 263 and 274 currently return when compareAndSet(false, true) succeeds, which happens only on the first tap. This causes the first Accept/Decline action to be skipped, allowing only subsequent taps to proceed.

The condition needs to be inverted in both handleAccept and handleDecline methods:

Proposed fix
 private fun handleAccept(payload: VoipPayload) {
-        if (acceptDeclineGuard.compareAndSet(false, true)) return
+        if (!acceptDeclineGuard.compareAndSet(false, true)) return
         if (BuildConfig.DEBUG) {
             Log.d(TAG, "Call accepted - callId: ${payload.callId}")
         }
@@
 private fun handleDecline(payload: VoipPayload) {
-        if (acceptDeclineGuard.compareAndSet(false, true)) return
+        if (!acceptDeclineGuard.compareAndSet(false, true)) return
         if (BuildConfig.DEBUG) {
             Log.d(TAG, "Call declined - callId: ${payload.callId}")
         }
🤖 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/IncomingCallActivity.kt`
around lines 262 - 280, The accept/decline guard in handleAccept and
handleDecline is inverted: currently the methods return when
acceptDeclineGuard.compareAndSet(false, true) succeeds (swallowing the first
tap). Change the checks in both IncomingCallActivity::handleAccept and
IncomingCallActivity::handleDecline to return only when compareAndSet(...)
returns false (i.e., negate the condition so the first successful compareAndSet
proceeds and subsequent taps are ignored).
🤖 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/IncomingCallActivity.kt`:
- Around line 262-280: The accept/decline guard in handleAccept and
handleDecline is inverted: currently the methods return when
acceptDeclineGuard.compareAndSet(false, true) succeeds (swallowing the first
tap). Change the checks in both IncomingCallActivity::handleAccept and
IncomingCallActivity::handleDecline to return only when compareAndSet(...)
returns false (i.e., negate the condition so the first successful compareAndSet
proceeds and subsequent taps are ignored).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bfae5ee1-ce02-4fe4-a146-44d595936dae

📥 Commits

Reviewing files that changed from the base of the PR and between eb33c97 and 9a46598.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.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 comments (2)
android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt (1)

73-80: LGTM — foreground startup now refreshes on every ACTION_START.

This matches the PR objective and keeps each start intent paired with a foreground notification update.

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

6-49: Good shared guard state for accept/decline.

A single AtomicBoolean is the right shape here because accept and decline must be mutually exclusive across rapid taps.

@diegolmello diegolmello force-pushed the fix/c3-c4-android-voiplib branch from 9a46598 to 9630f66 Compare April 23, 2026 13:02
@diegolmello diegolmello requested a deployment to approve_e2e_testing April 23, 2026 13:02 — with GitHub Actions Waiting
Copy link
Copy Markdown
Contributor

@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.

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/IncomingCallActivity.kt (1)

262-283: ⚠️ Potential issue | 🔴 Critical

Critical: inverted guard — first tap now does nothing, only subsequent taps trigger accept/decline.

AtomicBoolean.compareAndSet(false, true) returns true when the update succeeds, i.e. on the first invocation. As written, the first tap hits the early return and never calls handleAcceptAction / handleDeclineAction / clearTimeout / finish(). The second tap finds the guard already true, compareAndSet returns false, and the handler runs — but now the guard is set so no further invocation can proceed either. Net effect: Accept/Decline appears dead on first tap and the "double-tap guard" is inverted into a "single-tap block."

Negate the condition:

🐛 Proposed fix
     private fun handleAccept(payload: VoipPayload) {
-        if (acceptDeclineGuard.compareAndSet(false, true)) return
+        if (!acceptDeclineGuard.compareAndSet(false, true)) return
         if (BuildConfig.DEBUG) {
             Log.d(TAG, "Call accepted - callId: ${payload.callId}")
         }
@@
     private fun handleDecline(payload: VoipPayload) {
-        if (acceptDeclineGuard.compareAndSet(false, true)) return
+        if (!acceptDeclineGuard.compareAndSet(false, true)) return
         if (BuildConfig.DEBUG) {
             Log.d(TAG, "Call declined - callId: ${payload.callId}")
         }

Worth adding an instrumentation/unit test that taps Accept once and asserts handleAcceptAction was invoked — the PR test plan ("Kotlin syntax check passes, Android build succeeds") would not have caught this.

🤖 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/IncomingCallActivity.kt`
around lines 262 - 283, The click-guard is inverted: change the early-return
condition in both handleAccept and handleDecline so the handler runs only when
compareAndSet successfully flips the flag; specifically replace the current if
(acceptDeclineGuard.compareAndSet(false, true)) return with a negated check
(e.g., if (!acceptDeclineGuard.compareAndSet(false, true)) return) in both
handleAccept and handleDecline so clearTimeout, VoipNotification.cancelTimeout
and VoipNotification.handleAcceptAction / VoipNotification.handleDeclineAction
(and finish() in handleDecline) run on the first tap.
🤖 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/IncomingCallActivity.kt`:
- Around line 262-283: The click-guard is inverted: change the early-return
condition in both handleAccept and handleDecline so the handler runs only when
compareAndSet successfully flips the flag; specifically replace the current if
(acceptDeclineGuard.compareAndSet(false, true)) return with a negated check
(e.g., if (!acceptDeclineGuard.compareAndSet(false, true)) return) in both
handleAccept and handleDecline so clearTimeout, VoipNotification.cancelTimeout
and VoipNotification.handleAcceptAction / VoipNotification.handleDeclineAction
(and finish() in handleDecline) run on the first tap.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d1f2104-eb9c-486d-a39e-82ab29ba0a29

📥 Commits

Reviewing files that changed from the base of the PR and between 9a46598 and 9630f66.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipCallService.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 (1)
📓 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

@diegolmello diegolmello requested a deployment to experimental_ios_build April 23, 2026 13:06 — with GitHub Actions Waiting
@diegolmello diegolmello temporarily deployed to experimental_android_build April 23, 2026 13:06 — with GitHub Actions Inactive
@diegolmello diegolmello requested a deployment to official_android_build April 23, 2026 13:06 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to upload_experimental_android April 23, 2026 13:46 — with GitHub Actions Waiting
@github-actions
Copy link
Copy Markdown

Android Build Available

Rocket.Chat Experimental 4.72.0.108606

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNQmj-H1FsKwH46Q-EZ_NGJhbKshN-iJTrEcI8xEYF-NuC6DZhCpGh1DArhk9pI521o3H6MP6kwA-yqbNkyk

@diegolmello diegolmello merged commit 82a157e into feat.voip-lib-new Apr 23, 2026
8 of 13 checks passed
@diegolmello diegolmello deleted the fix/c3-c4-android-voiplib branch April 23, 2026 13:48
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