Skip to content

feat(Android): Add full screen incoming call#6977

Merged
diegolmello merged 14 commits into
feat.voip-lib-newfrom
feat.voip-fs-incoming-call
Feb 17, 2026
Merged

feat(Android): Add full screen incoming call#6977
diegolmello merged 14 commits into
feat.voip-lib-newfrom
feat.voip-fs-incoming-call

Conversation

@diegolmello

@diegolmello diegolmello commented Feb 11, 2026

Copy link
Copy Markdown
Member

Proposed changes

Adds a proper full screen incoming call to Voice.
Supports both light and dark themes.

Missing:

  • swipe to accept/reject
  • review tablet support

Issue(s)

https://rocketchat.atlassian.net/browse/VMUX-37

How to test or reproduce

Screenshots

locked_app_background.mov
locked_app_closed_dark_theme.mov

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

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

Summary by CodeRabbit

  • New Features

    • Redesigned incoming call screen with header, avatar container, caller name and host, and clear accept/reject actions
    • Caller avatars load dynamically, support rounded corners and adjustable sizing; caller username and host surfaced in incoming call payload
    • Applied Inter typography, new call icons, updated light/dark color themes, and accessibility label for avatar
  • Chores

    • Added image-loading library dependency
    • Minor formatting and style/background adjustments

diegolmello and others added 5 commits February 11, 2026 11:29
…display, streamline avatar loading logic, and add username field to VoipPayload for improved avatar handling. Update layout to reflect changes in avatar ImageView ID.
…able avatar sizes and refactor IncomingCallActivity to utilize new avatar URI method for improved avatar loading logic.
@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an Android incoming-call UI with resources and styles, Glide dependency, avatar URI sizing and caller factory in Ejson, VoipPayload extended with username/hostName, IncomingCallActivity avatar loading (Glide) and rounded corners, plus minor JS/CI formatting tweaks.

Changes

Cohort / File(s) Summary
Dependencies
android/app/build.gradle
Added Glide image-loading dependency: implementation "com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}".
Avatar URI / Notification helper
android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
Made buildAvatarUri size-aware (sizePx), added forCallerAvatar(String host, String username) and getCallerAvatarUri(int sizePx); null-safe server URL handling and default-size fallback.
VoIP Data Model
android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt
Added username and hostName fields; updated constructor and (de)serialization (toBundle, toWritableMap, fromMap, fromBundle) to include new fields.
Incoming Call Activity
android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt
New incoming-call UI flow: payload validation, Inter font application, avatar loading via Glide with rounded corners, dynamic caller/host display, refactored button handlers and ringtone lifecycle.
Layout & Drawables
android/app/src/main/res/layout/activity_incoming_call.xml, android/app/src/main/res/drawable/bg_avatar_incoming_call.xml, .../bg_btn_accept.xml, .../bg_btn_reject.xml, .../ic_call.xml, .../ic_call_end.xml
Added incoming-call layout (header, centered avatar/name/host, bottom accept/reject), and supporting shape/vector drawables for avatar and action buttons.
UI Resources (Colors, Strings, Styles)
android/app/src/main/res/values/colors_incoming_call.xml, .../values-night/colors_incoming_call.xml, .../values/strings_incoming_call.xml, .../values/styles_incoming_call.xml
Added light/night color palettes and strings for incoming call UI; updated Theme.IncomingCall window background to @color/incoming_call_background.
JS Formatting / Minor
app/sagas/deepLinking.js, app/sagas/login.js, index.js
Parenthesized single arrow-function parameters (stylistic only); no behavioral changes.
CI Workflow
.github/workflows/prettier.yml
Formatting commit message changed to remove [skip ci].

Sequence Diagram

sequenceDiagram
    participant Activity as IncomingCallActivity
    participant Payload as VoipPayload
    participant Ejson as Ejson
    participant Glide as Glide
    participant Server as AvatarServer

    Activity->>Payload: read username, hostName
    Activity->>Activity: validate payload
    Activity->>Ejson: forCallerAvatar(hostName, username)
    Ejson-->>Activity: Ejson instance
    Activity->>Ejson: getCallerAvatarUri(sizePx)
    Ejson-->>Activity: avatar URI
    Activity->>Glide: load(avatarUri) into ImageView
    Glide->>Server: fetch image
    Server-->>Glide: return image
    Glide->>Glide: apply rounded corners
    Glide-->>Activity: deliver bitmap (success/fail)
    Activity->>Activity: show/hide avatar, update UI
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped to fetch a face so neat,
a tiny URI and rounded cheek,
fonts dressed bold, colors set just right,
Glide fetched the smile into the night,
Incoming rings — I twitched with delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% 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 'feat(Android): Add full screen incoming call' directly and clearly summarizes the main change in the changeset.
Linked Issues check ✅ Passed The PR implements the full-screen incoming call UI on Android VMUX-37, with payload validation, avatar loading, font styling, button handling, and theme support for both light and dark modes.
Out of Scope Changes check ✅ Passed Minor formatting changes (parentheses around arrow function parameters) in unrelated files (deepLinking.js, login.js, index.js, prettier.yml) are present but do not materially impact the scope.

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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt`:
- Around line 70-71: The fromMap function currently returns null when
data["username"] is missing, causing payloads to be dropped; change fromMap in
VoipPayload.kt to treat username as optional and default to an empty string
(consistent with the username field default and fromBundle), i.e. replace the
early-return behavior in fromMap with assigning username = data["username"] ?:
"" (or equivalent) so the payload is not discarded when username is omitted;
keep hostName handling as-is and ensure the constructed VoipPayload uses this
defaulted username.
- Around line 16-27: The fromMap() implementation of VoipPayload currently
requires "username" (and treats it differently than fromBundle()), causing null
returns when the server omits it; update fromMap() in class VoipPayload to treat
username (and hostName) as optional by reading map["username"] as? String ?: ""
(and map["hostName"] as? String ?: "") instead of returning null, while keeping
host and type validation intact so only required fields still cause null.

In `@android/app/src/main/res/layout/activity_incoming_call.xml`:
- Around line 52-58: The layout currently hardcodes accessibility and default
texts — replace the literal "Caller avatar", "Unknown caller", and "Unknown
host" with string resources: add entries (e.g., caller_avatar, unknown_caller,
unknown_host) to strings_incoming_call.xml and update the layout attributes (for
the ImageView with android:id="@+id/avatar" use
android:contentDescription="@string/caller_avatar" and replace any hardcoded
text attributes in this XML that show "Unknown caller"/"Unknown host" with
`@string/unknown_caller` and `@string/unknown_host`) so localization and TalkBack
use resource strings.

In `@android/app/src/main/res/values-night/colors_incoming_call.xml`:
- Around line 5-8: The night theme color resource names don't match the ones
used by the layout, so rename the color entries in the night colors file: change
incoming_call_status_text -> incoming_call_header_text,
incoming_call_status_icon -> incoming_call_header_icon, and incoming_call_name
-> incoming_call_host_name so the night overrides are picked up; optionally keep
or remove incoming_call_org if unused (or rename it only if the layout actually
references a corresponding key).
🧹 Nitpick comments (8)
android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (1)

112-121: forCallerAvatar creates a partially initialized Ejson — consider adding a guard or documenting the limitation.

The factory sets only host and caller.username, leaving all other fields (type, sender, rid, etc.) as null. Calling getAvatarUri() on this instance would NPE or return null silently since type is null. Current usage only calls getCallerAvatarUri(), so it works fine today, but a future caller could easily misuse it.

A brief Javadoc note or an assertion would help prevent this.

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

111-142: Consider importing Glide types instead of using fully-qualified class names inline.

The CustomTarget, Drawable, and Transition types are referenced with fully-qualified names inside the anonymous object (lines 122-125, 132, 137). Importing them would improve readability.

♻️ Proposed refactor — add imports and simplify

Add these imports at the top of the file:

import android.graphics.drawable.Drawable
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition

Then simplify the callback:

         Glide.with(this)
             .load(avatarUrl)
-            .into(object : com.bumptech.glide.request.target.CustomTarget<android.graphics.drawable.Drawable>(sizePx, sizePx) {
+            .into(object : CustomTarget<Drawable>(sizePx, sizePx) {
                 override fun onResourceReady(
-                    resource: android.graphics.drawable.Drawable,
-                    transition: com.bumptech.glide.request.transition.Transition<in android.graphics.drawable.Drawable>?
+                    resource: Drawable,
+                    transition: Transition<in Drawable>?
                 ) {
                     imageView.visibility = View.VISIBLE
                     imageView.setImageDrawable(resource)
                     applyAvatarRoundCorners(imageView, cornerRadiusPx)
                 }
 
-                override fun onLoadFailed(errorDrawable: android.graphics.drawable.Drawable?) {
+                override fun onLoadFailed(errorDrawable: Drawable?) {
                     imageView.visibility = View.GONE
                 }
 
-                override fun onLoadCleared(placeholder: android.graphics.drawable.Drawable?) {
+                override fun onLoadCleared(placeholder: Drawable?) {
                     imageView.visibility = View.GONE
                 }
             })

207-217: TODO: Decline call via REST API is unimplemented — the caller will keep ringing.

Line 214 notes a TODO to call the REST API to decline the call. Without this, pressing "Decline" only dismisses the local UI and notification but does not inform the server, so the caller's phone will continue ringing until timeout. This will lead to a poor UX for both parties.

Would you like me to open an issue to track this? Or do you want me to help sketch out the REST API call implementation?


149-160: Nit: redundant local variable radius.

Line 152 assigns val radius = cornerRadiusPx but cornerRadiusPx could be used directly. The lambda captures the parameter just fine.

♻️ Minor cleanup
     private fun applyAvatarRoundCorners(imageView: ImageView, cornerRadiusPx: Float) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return
         imageView.post {
-            val radius = cornerRadiusPx
             imageView.outlineProvider = object : ViewOutlineProvider() {
                 override fun getOutline(view: View, outline: android.graphics.Outline) {
-                    outline.setRoundRect(0, 0, view.width, view.height, radius)
+                    outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx)
                 }
             }
             imageView.clipToOutline = true
         }
     }

224-226: Migrate onBackPressed() to OnBackInvokedCallback for API 33+ compatibility.

onBackPressed() is deprecated starting with Android 13 (API 33). Since the app targets SDK 35, use OnBackInvokedCallback with getOnBackInvokedDispatcher() instead. While the current code compiles and functions, modernizing this ensures consistency with Android's back gesture handling framework.

Example migration for API 33+
private val onBackInvokedCallback = OnBackInvokedCallback {
    voipPayload?.let { handleDecline(it) } ?: finish()
}

override fun onStart() {
    super.onStart()
    onBackInvokedDispatcher.registerOnBackInvokedCallback(
        OnBackInvokedDispatcher.PRIORITY_DEFAULT,
        onBackInvokedCallback
    )
}

override fun onStop() {
    onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackInvokedCallback)
    super.onStop()
}
android/app/src/main/res/layout/activity_incoming_call.xml (3)

17-24: android:tint is deprecated — use app:tint for backward compatibility.

android:tint (line 24) was added in API 21 and has inconsistent behavior across vendors. The AppCompat app:tint attribute (from xmlns:app="http://schemas.android.com/apk/res-auto") provides consistent tinting via AppCompatImageView.

♻️ Proposed fix

Add the app namespace to the root LinearLayout:

xmlns:app="http://schemas.android.com/apk/res-auto"

Then replace the tint attribute:

-            android:tint="@color/incoming_call_header_icon" />
+            app:tint="@color/incoming_call_header_icon" />

Also consider changing the ImageView to androidx.appcompat.widget.AppCompatImageView for guaranteed compat tinting.


46-50: Negative margin (-60dp) can cause clipping on some OEM skins and older APIs.

The android:layout_marginTop="-60dp" on the avatar FrameLayout shifts it upward to visually overlap with the header area. While this works on most stock Android devices, some OEM implementations (Samsung OneUI, Xiaomi MIUI) may clip content at parent boundaries when negative margins are used within a LinearLayout.

Consider using a ConstraintLayout for the center section to achieve the overlap effect with constraints instead of negative margins, or verify this renders correctly across target devices.


10-15: Fixed paddingTop="108dp" may not adapt well to different screen sizes.

108dp is a significant fixed offset. On smaller devices (~480dp screen height), this consumes ~22% of the vertical space before any content appears. Combined with the bottom buttons' marginBottom="48dp", the layout may run out of room on compact screens.

Consider using a percentage-based offset (via ConstraintLayout guidelines) or a smaller top padding that scales better, especially since the PR notes that tablet support hasn't been reviewed yet.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d40944 and 930b852.

📒 Files selected for processing (17)
  • android/app/build.gradle
  • android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java
  • android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt
  • android/app/src/main/res/drawable/bg_avatar_incoming_call.xml
  • android/app/src/main/res/drawable/bg_btn_accept.xml
  • android/app/src/main/res/drawable/bg_btn_reject.xml
  • android/app/src/main/res/drawable/ic_call.xml
  • android/app/src/main/res/drawable/ic_call_end.xml
  • android/app/src/main/res/layout/activity_incoming_call.xml
  • android/app/src/main/res/values-night/colors_incoming_call.xml
  • android/app/src/main/res/values/colors_incoming_call.xml
  • android/app/src/main/res/values/strings_incoming_call.xml
  • android/app/src/main/res/values/styles_incoming_call.xml
  • app/sagas/deepLinking.js
  • app/sagas/login.js
  • index.js
🧰 Additional context used
🧬 Code graph analysis (3)
index.js (1)
app/lib/services/voip/MediaCallLogger.ts (1)
  • error (14-16)
android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (4)
android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt (1)
  • TAG (28-109)
android/app/src/main/java/chat/rocket/reactnative/notification/NotificationIntentHandler.kt (1)
  • TAG (16-186)
android/app/src/main/java/chat/rocket/reactnative/notification/RCFirebaseMessagingService.kt (1)
  • TAG (18-79)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt (1)
  • TAG (14-75)
android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt (1)
app/definitions/Voip.ts (1)
  • VoipPayload (7-13)
🔇 Additional comments (15)
app/sagas/deepLinking.js (1)

49-49: Formatting-only change — looks good.

Parenthesizing the single arrow-function parameter aligns with the project's style consistency effort across the other files in this PR.

index.js (1)

50-50: Formatting-only change — LGTM.

app/sagas/login.js (1)

63-63: All formatting-only changes — LGTM.

Consistent parenthesization of single arrow-function parameters across the WatermelonDB callbacks. No behavioral change.

Also applies to: 110-110, 282-282, 287-287, 364-364

android/app/build.gradle (1)

151-151: LGTM!

Glide dependency addition looks correct. The version is properly externalized to rootProject.ext.glideVersion.

android/app/src/main/res/drawable/ic_call.xml (1)

1-9: LGTM!

Standard vector drawable definition.

android/app/src/main/res/values/colors_incoming_call.xml (1)

1-15: LGTM!

Light theme color definitions are well-structured with clear naming. See the comment on the night-theme file for a naming mismatch that needs to be resolved.

android/app/src/main/res/drawable/bg_avatar_incoming_call.xml (1)

1-6: LGTM!

android/app/src/main/res/drawable/bg_btn_accept.xml (1)

1-6: LGTM!

android/app/src/main/res/drawable/bg_btn_reject.xml (1)

1-6: LGTM!

android/app/src/main/res/drawable/ic_call_end.xml (1)

1-9: LGTM!

Standard vector drawable definition.

android/app/src/main/res/values/styles_incoming_call.xml (1)

7-7: LGTM!

The window background now correctly references the theme-aware color resource, aligning with the DayNight parent theme for consistent light/dark mode support.

android/app/src/main/res/values/strings_incoming_call.xml (1)

1-6: LGTM!

String resources are well-named and correctly use the Unicode ellipsis character. Layout references align with these IDs.

android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java (2)

60-76: LGTM — buildAvatarUri null-safety and size parameterization.

Good improvement: early return when serverURL is null/empty prevents NPE downstream. The size parameter is cleanly integrated without breaking existing callers.


127-148: LGTM — overloaded getCallerAvatarUri with default size.

Clean backward-compatible overload. The default delegates to the parameterized version correctly.

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

80-102: LGTM — font loading with graceful fallback.

Good approach: if Bold fails, it falls back to Regular; if Regular fails, the method returns early without crashing. The null-safe ?.setTypeface calls also prevent NPE if a view ID is missing from the layout.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt Outdated
Comment thread android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt Outdated
Comment thread android/app/src/main/res/layout/activity_incoming_call.xml Outdated
Comment thread android/app/src/main/res/values-night/colors_incoming_call.xml Outdated

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/prettier.yml:
- Around line 49-50: The workflow currently pushes a formatting commit using the
git commit command which removed the "[skip ci]" token and thus retriggers the
workflow; update the commit invocation in .github/workflows/prettier.yml (the
git commit -m "chore: format code and fix lint issues" step) to include "[skip
ci]" in the message (e.g., "chore: format code and fix lint issues [skip ci]")
so the push does not trigger a redundant CI run, or alternatively reintroduce an
equivalent mechanism (adding "[ci skip]" or using git push options) to prevent
the workflow from self-triggering.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 930b852 and 5f479b3.

📒 Files selected for processing (1)
  • .github/workflows/prettier.yml
⏰ 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). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread .github/workflows/prettier.yml

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@android/app/src/main/res/layout/activity_incoming_call.xml`:
- Around line 44-59: The FrameLayout containing the ImageView (id "avatar")
lacks the fallback initial, so add a TextView inside the same FrameLayout as the
ImageView to display the caller's initial (centered, matching the 120dp size)
and use it as the visible default while the ImageView remains gone; ensure the
TextView has an id (e.g., "avatar_initial"), appropriate textAppearance/size,
centered gravity, accessible contentDescription, and that your loading logic
will swap visibility (hide "avatar_initial" and show "avatar" when the image
loads successfully).
🧹 Nitpick comments (1)
android/app/src/main/res/layout/activity_incoming_call.xml (1)

45-49: Negative top margin is fragile and may cause clipping.

android:layout_marginTop="-60dp" (line 48) is used to visually shift the avatar upward into the header area. Negative margins are not officially guaranteed to work consistently across all Android layout managers and can cause the view to be clipped by its parent. Consider using a ConstraintLayout with vertical bias or translationY for a more robust overlap effect.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f479b3 and d04f155.

📒 Files selected for processing (4)
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt
  • android/app/src/main/res/layout/activity_incoming_call.xml
  • android/app/src/main/res/values-night/colors_incoming_call.xml
  • android/app/src/main/res/values/strings_incoming_call.xml
🚧 Files skipped from review as they are similar to previous changes (3)
  • android/app/src/main/res/values/strings_incoming_call.xml
  • android/app/src/main/res/values-night/colors_incoming_call.xml
  • android/app/src/main/java/chat/rocket/reactnative/voip/VoipPayload.kt
⏰ 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/res/layout/activity_incoming_call.xml (2)

83-175: Action buttons look good — adequate touch targets and proper accessibility.

Both decline and accept buttons use focusable, clickable, contentDescription via string resources, and sufficient padding (16dp around 64dp icons) for comfortable touch targets. The spacer-based layout correctly pushes them to opposite sides.


17-23: Use app:tint instead of deprecated android:tint.

android:tint on ImageView is flagged by AppCompat lint rules as deprecated. Use app:tint from the AndroidX/AppCompat namespace for proper tinting. You'll need to add xmlns:app="http://schemas.android.com/apk/res-auto" to the root element.

Proposed fix

Add namespace to root:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"

Then in the ImageView:

-            android:tint="@color/incoming_call_header_icon" />
+            app:tint="@color/incoming_call_header_icon" />

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread android/app/src/main/res/layout/activity_incoming_call.xml Outdated

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

Actionable comments posted: 1

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)

207-217: ⚠️ Potential issue | 🟠 Major

TODO: Decline doesn't notify the server — call may ring indefinitely on other devices.

Line 214 has a TODO to call the REST API on decline. Without this, declining only stops the local ringtone and dismisses the notification. The caller and any other devices will continue ringing. Ensure this is tracked for follow-up.

Would you like me to open an issue to track implementing the server-side decline call?

🤖 Fix all issues with AI agents
In
`@android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt`:
- Around line 106-107: The current calls in IncomingCallActivity.kt that set
caller and host TextViews use payload.caller.ifEmpty { "" } and
payload.hostName.ifEmpty { "" }, which are no-ops; either assign payload.caller
and payload.hostName directly to the TextViews, or provide a real fallback
(e.g., use payload.caller.ifBlank { "Unknown Caller" } and
payload.hostName.ifBlank { "Unknown Host" } or another meaningful default) so
the UI shows a usable name; update the two assignments that call
findViewById<TextView>(...) to use the chosen approach.
🧹 Nitpick comments (5)
android/app/src/main/res/layout/activity_incoming_call.xml (2)

17-23: android:tint is deprecated — prefer app:tint with AppCompatImageView.

android:tint on ImageView is deprecated. Use app:tint (from the androidx.appcompat namespace) to ensure consistent tinting across API levels. You'll also need to add the app namespace to the root element.

Suggested fix

Add namespace to root:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"

Then on the ImageView:

-            android:tint="@color/incoming_call_header_icon" />
+            app:tint="@color/incoming_call_header_icon" />

91-101: Ensure adequate touch target size for action buttons.

The 64dp icon is wrapped in a LinearLayout with 16dp padding, giving a reasonable touch area. However, on the reject button the layout_marginStart="44dp" and on accept layout_marginEnd="44dp" push buttons close to edges on narrow screens. Verify this works well on small-screen devices (≤ 320dp width).

Also applies to: 136-146

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

122-141: Use imports instead of fully-qualified class names in the CustomTarget callback.

com.bumptech.glide.request.target.CustomTarget, android.graphics.drawable.Drawable, and com.bumptech.glide.request.transition.Transition are spelled out inline. Import them at the top of the file for readability.

Suggested fix

Add to imports:

import android.graphics.drawable.Drawable
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition

Then simplify:

-        Glide.with(this)
-            .load(avatarUrl)
-            .into(object : com.bumptech.glide.request.target.CustomTarget<android.graphics.drawable.Drawable>(sizePx, sizePx) {
-                override fun onResourceReady(
-                    resource: android.graphics.drawable.Drawable,
-                    transition: com.bumptech.glide.request.transition.Transition<in android.graphics.drawable.Drawable>?
-                ) {
+        Glide.with(this)
+            .load(avatarUrl)
+            .into(object : CustomTarget<Drawable>(sizePx, sizePx) {
+                override fun onResourceReady(
+                    resource: Drawable,
+                    transition: Transition<in Drawable>?
+                ) {

And similarly for onLoadFailed / onLoadCleared parameter types.


149-160: Remove unnecessary Lollipop guard and redundant variable.

The project's minSdkVersion is 24 (Android 7.0), making the Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP check on line 150 dead code. Additionally, val radius = cornerRadiusPx is a redundant alias — use the parameter directly in the setRoundRect call.

Suggested fix
     private fun applyAvatarRoundCorners(imageView: ImageView, cornerRadiusPx: Float) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return
         imageView.post {
-            val radius = cornerRadiusPx
             imageView.outlineProvider = object : ViewOutlineProvider() {
                 override fun getOutline(view: View, outline: android.graphics.Outline) {
-                    outline.setRoundRect(0, 0, view.width, view.height, radius)
+                    outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx)
                 }
             }
             imageView.clipToOutline = true
         }
     }

224-226: onBackPressed() is deprecated since API 33. Use OnBackPressedDispatcher with an OnBackPressedCallback instead.

Register the callback in onCreate after setContentView:

onBackPressedDispatcher.addCallback(
  this,
  object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
      voipPayload?.let { handleDecline(it) } ?: finish()
    }
  }
)

Then remove the onBackPressed() override entirely.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d04f155 and 6c879b8.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt
  • android/app/src/main/res/layout/activity_incoming_call.xml
⏰ 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). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (3)
android/app/src/main/res/layout/activity_incoming_call.xml (1)

1-176: Layout looks good overall for the incoming call screen.

The three-section structure (header, center content, bottom actions) is clear, the spacer approach for button positioning works, and string/color resources are properly referenced. The tools:text usage for preview-only defaults is correct.

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

81-103: Font loading approach is reasonable with proper fallbacks.

The try/catch pattern for font loading with fallback from Inter-Bold to Inter-Regular is well-handled. Early return if the base font fails is appropriate.


38-79: onCreate flow is well-structured.

Good defensive checks: keyguard dismissal before content view, API-level branching for lock-screen flags, early exit on invalid payload. The flow is clear and handles edge cases properly.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread android/app/src/main/java/chat/rocket/reactnative/voip/IncomingCallActivity.kt Outdated
@github-actions

Copy link
Copy Markdown

Android Build Available

Rocket.Chat Experimental 4.69.0.108258

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

@diegolmello diegolmello merged commit 18b0b0a into feat.voip-lib-new Feb 17, 2026
2 of 5 checks passed
@diegolmello diegolmello deleted the feat.voip-fs-incoming-call branch February 17, 2026 16:47
diegolmello added a commit that referenced this pull request Apr 22, 2026
…/Decline (#7215)

* merge feat.voip-lib

* feat(voip): enhance call handling with UUID mapping and event listeners

* Base call UI

* feat(voip): integrate Zustand for call state management and enhance CallView UI

* feat(voip): add simulateCall function for mock call handling in UI development

* refactor(CallView): update button handlers and improve UI responsiveness

* Add pause-shape-unfilled icon

* Base CallHeader

* toggleFocus

* collapse buttons

* Header components

* Hide header when no call

* Timer

* Add use memo

* Add voice call item on sidebar

* cleanup

* Temp use @rocket.chat/media-signaling from .tgz

* cleanup

* Check module and permissions to enable voip

* Refactor stop method to use optional chaining for media signal listeners

* voip push first test

* Add VoIP call handling with pending call management

- Implemented VoIP push notification handling in index.js, including storing call info for later processing.
- Added CallKeep event handlers for answering and ending calls from a cold start.
- Introduced a new CallIdUUID module to convert call IDs to deterministic UUIDs for compatibility with CallKit.
- Created a pending call store to manage incoming calls when the app is not fully initialized.
- Updated deep linking actions to include VoIP call handling.
- Enhanced MediaSessionInstance to process pending calls and manage call states effectively.

* Remove pending store and create getInitialEvents on app/index

* Attempt to make iOS calls work from cold state

* lint and format

* Patch callkeep ios

* Temp send iOS voip push token on gcm

* Temp fix require cycle

* chore: format code and fix lint issues [skip ci]

* CallIDUUID module on android and voip push

* Add setCallUUID on useCallStore to persist calls accepted on native Android

* remove callkeep from notification

* Android Incoming Call UI POC

* Refactor VoIP handling: Migrate VoIP-related classes to a new package structure, removing deprecated modules and consolidating functionality. Update imports in MainApplication and NotificationIntentHandler to reflect changes. This cleanup enhances code organization and prepares for future VoIP feature enhancements.

* Remove VoipForegroundService

* cleanup and use caller instead of callerName

* Cleanup and make iOS build again

* Refactor VoIP handling: Remove unused event emissions for call answered and declined, switch from SharedPreferences to in-memory storage for pending VoIP call data, and update method signatures for better clarity. This cleanup enhances performance and prepares for future VoIP feature improvements.

* Refactor VoIP handling: Introduce a new VoipPayload class to encapsulate call data, streamline notification processing, and enhance method signatures across the VoIP module. This update improves code clarity and prepares for future feature enhancements.

* Migrate react-native-voip-push-notifications to VoipModule

* Refactor VoIP module: Update package structure by moving VoipTurboPackage to the main package and removing the obsolete NativeVoipSpec class. Adjust imports in MainApplication and VoipModule to reflect these changes, enhancing code organization and maintainability.

* Unify emitters

* Move CallKeep listeners from MediaSessionInstance to getInitialEvents

* Clear callkeep on endcall

* Unify getInitialEvents logic

* getInitialEvents -> MediaCallEvents

* chore: format code and fix lint issues [skip ci]

* feat(Android): Add full screen incoming call (#6977)

* feat: Update call UI (#6990)

* feat: Handle audio routing, e.g., Bluetooth headset vs. internal speaker switching (#6992)

* fix: empty space when not on call (#6993)

* feat: Dialpad (#7000)

* action: organized translations

* feat: start call (#7024)

* chore: format code and fix lint issues

* feat: Pre flight (#7038)

* action: organized translations

* feat: Receive voip push notifications from backend (#7045)

* feat: Refactor media session handling and improve disconnect logic (#7065)

* feat: Control incoming call from native (#7066)

* feat: Voice message blocks (#7057)

* feat: native accept success event (#7068)

* feat(voip): call waiting, busy detection, and videoconf blocking (#7077)

* action: organized translations

* feat(voip): tap-to-hide call controls with animations (#7078)

* feat(voip): navigate to call DM from message button and header (#7082)

* feat(voip): tablet and landscape layout (#7110)

* chore: develop into feat.voip-lib-new (RN 81 + Expo 54 + reanimated 4 + true-sheet + iOS 26) (#7114)

* chore: format code and fix lint issues

* feat(voip): android landscape layout for IncomingCallActivity (#7116)

* Update agents files

* feat(voip): Support a11y (#7106)

* Fix content cutting on iOS on some edge cases

* pods

* Ignore .worktrees on jest

* chore: Merge develop into feat.voip-lib-new (#7129)

* fix(voip): show CallKit UI when call is active in background (#7128)

* chore: Update media-signaling to 0.2.0 (#7153)

* feat(voip): migrate iOS accept/reject from DDP to REST (#7124)

* Fix icons

* feat(voip): migrate Android accept/reject from DDP to REST (#7127)

* test(voip): integration tests for CallView pipeline (#7161)

* feat(voip): display video conf provider as subtitle (#7160)

* fix(voip): CallView button grid and correct landscape/dialpad layouts (#7164)

* fix(voip): prevent stale MMKV cache on Android first-install accept

MMKVKeyManager.initialize ran in MainApplication.onCreate before the JS
engine started and opened the default MMKV file via the Tencent 1.2 JAR
when it was still empty. Tencent caches instances per-ID in a singleton
registry, so that empty-state view was held for the rest of the process.
JS later wrote credentials through react-native-mmkv (MMKV Core 2.0),
which has its own separate registry. When a VoIP push arrived,
Ejson.getMMKV() got the cached empty Tencent instance and reported
"No userId found in MMKV for server". Closing and reopening the app
cleared the cache, which is why only the very first call after install
failed.

Drop the open/verify block — the encryption key is already cached from
SecureKeystore, so no MMKV handle is needed here. The first Tencent
instance is now created inside Ejson.getMMKV() after JS has written,
so it scans the file fresh.

* fix(voip): prevent duplicate ringtone on Android incoming call (#7158)

* fix(voip): set explicit snaps for NewMediaCall bottom sheet (#7165)

* Update app/lib/services/voip/MediaSessionStore.ts

Co-authored-by: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com>

* fix: make startVoipFork reactive to permissions-changed (#7151)

* fix(android): remove MediaProjectionService from merged manifest (#7190)

* fix(voip): Phone account creation (#7170)

* feat: add Enable Mobile Ringing toggle in user preferences (#7155)

* fix(voip): ship blockers for PushKit, licensing, outbound calls, push tokens (#7167)

* fix(android): Play Store mic discoverability, safer FCM logs, avatar auth via headers (#7171)

* fix(ios): serialize VoipService bridge statics (#7169)

* fix(voip): Android DDP thread safety and VoipPayload bundle parity (#7168)

* chore(voip): dead-code and hygiene sweep (#7174)

* refactor(voip): decouple navigateToCallRoom from Redux and backfill REST/connect tests (#7176)

* test(voip): tighten ringing endCall assertion and add VideoConf VoIP-lock saga coverage (#7177)

* fix(ios): harden VoIP DDP WebSocket client on receive failures and TLS (#7173)

* refactor(voip): MediaCallEvents Redux adapters and resetVoipState (#7178)

* refactor(voip): decouple peer autocomplete from Redux; simplify NewMediaCall (#7175)

* fix(ios): add NS_SWIFT_NAME to Challenge.runChallenge for Swift 6.2 compatibility

Swift 6.2 (Xcode 26.x / macos-26 runner) auto-renames the Objective-C
method runChallenge:didReceiveChallenge:completionHandler: to
run(_:didReceive:completionHandler:) when imported into Swift.

Add NS_SWIFT_NAME to explicitly pin the Swift import name, preventing
the compiler from applying its heuristics. This keeps the existing
Swift call site in DDPClient.swift working without changes.

* fix(ios): cancel old URLSession/webSocketTask before reconnecting in DDPClient.connect (#7197)

* fix(ios): add NSLock to nativeAcceptHandledCallIds and 10s REST timeout to handleNativeAccept (#7198)

* feat(android): create VoipCallService with FOREGROUND_SERVICE_MICROPHONE (#7199)

* fix(android): start VoipCallService on accept, stop on hangup/timeout, install end-call listener (#7200)

* fix(voip): enable DM nav for users with SIP extension (#7203)

* fix(android): handle null VoiceConnection in answerIncomingCall, notify JS (#7201)

* fix(voip): resolve closure capture ordering in handleNativeAccept (#7209)

* fix(android): integrate VoIP modules with SSL-pinned OkHttpClient (#7208)

* fix(push): gate id and voipToken behind server version checks, fix VideoConf caller extra (#7210)

* fix(voip): remove sensitive data from production logs (#7207)

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

- VoipCallService: remove if (!isRunning) guard, call startForeground unconditionally
  (idempotent on Android, fixes Android 14+ foreground service requirement)
- IncomingCallActivity: add AtomicBoolean guard on handleAccept/handleDecline
  to prevent double-tap from triggering multiple service starts

---------

Co-authored-by: diegolmello <diegolmello@users.noreply.github.com>
Co-authored-by: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants