Skip to content

feat(chat): integrate Calls with Android Telecom - #6821

Open
thecyberd3m0n wants to merge 17 commits into
nextcloud:masterfrom
thecyberd3m0n:feat/Android-Telecom
Open

thecyberd3m0n wants to merge 17 commits into
nextcloud:masterfrom
thecyberd3m0n:feat/Android-Telecom

Conversation

@thecyberd3m0n

@thecyberd3m0n thecyberd3m0n commented Oct 5, 2026 •

Copy link
Copy Markdown

Resolves #6551. Telecom feature will work only on devices that support it

🖼️ Screenshots

🏚️ Before 🏡 After
Calls from were not displayed on Wear OS watch as Calls (no phone icon, no way to redirect sound to Pixel watch) Calls are visible on Wear OS watch, phone icon displays, and I can reroute the sound to it

🚧 TODO

  • test it with Android Auto

🤖 AI

  • 4 unit tests was generated using AI
  • beside of tests, AI was used in read-only mode

Signed-off-by: Dmitrij Rysanow <thecyberd3m0n@gmail.com>
Signed-off-by: Dmitrij Rysanow <thecyberd3m0n@gmail.com>
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e9e0834e-e90d-4f1b-b00b-244908fd7347
📥 Commits

Reviewing files that changed from the base of the PR and between b5c5425 and 329be2a.

📒 Files selected for processing (1)
  • app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

📝 Walkthrough

The app adds Core-Telecom registration and call-control operations. Incoming-call notifications register calls and route answer and reject callbacks to call actions. CallActivity registers outgoing calls when joined and ends Telecom calls during hangup. The manifest and foreground service add phone-call service configuration for Android 14 and later. CallActivity also updates task reuse and fallback behavior across Android versions.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 329be

A disconnect from a watch or Bluetooth device now ends the Talk call, and ending the call from the app does not re-trigger the hangup. No remaining merge-blocking risk was found in this review.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to b5c54

Platform call controls gain authority over Talk calls, but incoming-call ownership is not fully transferred or cleaned up. Ending a call from a watch or another platform surface may not stop the Talk session, and dismissing an incoming call can leave its platform session available for answering. The exposure is limited to the affected device and call rather than arbitrary accounts or server-wide access.

Retained concerns

  • High · security · inferred: An incoming registration keeps its notification-activity rejection callback after the call is answered. CallActivity reuses that registration through setCallActive without installing its active-call disconnect handler. A subsequent platform disconnect clears TelecomManager state but reaches only notification hangup, which finishes that screen, or is skipped by its lifecycle guard. It does not invoke Talk's media and network teardown. Consequently, an established incoming Talk session may continue after the user ends the platform call. The outgoing registration path does invoke Talk hangup, but that protection does not cover the reused incoming registration.
  • Medium · security · inferred: Declining an incoming call in the app or finishing its screen after notification cancellation does not terminate the new Telecom registration. The manager-owned session can remain occupied, excluding subsequent registrations until platform cleanup occurs. Its answer callback also has no leaving, finishing, or destroyed guard and can still launch CallActivity while that session remains live. This separates user dismissal from call authority and resource ownership. Unconditional cleanup on every activity destruction would not be sufficient because answering must deliberately transfer ownership to the active call.

Security review details

Security Blast Radius

  • inferred — The demonstrated control exposure concerns the registered call on one app installation. TelecomManager tracks only one registration, and neither call activity has an external intent filter. No arbitrary-app entrypoint or server-wide authority expansion was established. Stale external sessions could nevertheless outlive that single locally tracked slot.

Security Findings and Attack Paths

  • inferred — The privacy-relevant path is a legitimate incoming call followed by platform hangup after answering: the reused registration invokes notification rejection rather than active media teardown. Separately, app-side decline can leave a live platform answer callback. These are newly exposed lifecycle-control paths, not demonstrated remote exploits; they require an existing call and platform interaction.

Trust Boundaries and Controls

  • observed — The incoming screen resolves its bound account before registering a call. The manager rejects blank room identity, excludes concurrent registration, ignores termination aimed at another current room, and protects cleanup with a registration UUID. These checks protect tracked state but do not transfer callback ownership when the notification screen hands off to CallActivity.

Resilience and Maintainability Implications

  • observed — Local termination clears manager ownership before asynchronous platform disconnect completes and does not inspect its result. Stale scope assignment requests a CANCELED disconnect, also without checking completion. Registration guards prevent stale cleanup from clearing replacement fields, but successful disposal of the external resource is not established by those guards.

Hardening Proposals

  • proposed — Make incoming-to-active ownership transfer explicit, route disconnect to the current media owner, and distinguish decline or cancellation from successful handoff. Bind callback authority and disconnect suppression to the registration identity, and define recoverable transitions for failed platform activation or termination.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning [#6551] The PR adds Core-Telecom registration, incoming and outgoing call handling, disconnect callbacks, and foreground-service support. However, CallNotificationActivity.onCreate registers an inco… Register incoming calls when the app receives the incoming-call event, before it launches CallNotificationActivity. Register outgoing calls when dialing starts, then update Telecom state as setup progresses. Add tests for both call-flow i…
Docstring Coverage ⚠️ Warning Docstring coverage is 5.13% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: integrating calls with Android Telecom.
Description check ✅ Passed The description explains the issue, reported user-visible result, Android Auto TODO, and AI use. It includes the screenshots and TODO sections, but omits the checklist section and its test, capability…
Out of Scope Changes check ✅ Passed The Telecom dependency, permissions, manager, call-flow callbacks, foreground-service changes, and tests support [#6551]. The CallActivity task-selection changes support returning to the app during …
Full details: Linked Issues check

Explanation

[#6551] The PR adds Core-Telecom registration, incoming and outgoing call handling, disconnect callbacks, and foreground-service support. However, CallNotificationActivity.onCreate registers an incoming call only after the existing notification activity starts. This does not provide Telecom handling independent of the custom notification and full-screen-intent path requested in [#6551]. CallActivity.handleCallStateJoined registers an outgoing call only after the call joins, not when dialing starts. The new tests cover TelecomManager; they do not cover these call-flow integration points.

Resolution

Register incoming calls when the app receives the incoming-call event, before it launches CallNotificationActivity. Register outgoing calls when dialing starts, then update Telecom state as setup progresses. Add tests for both call-flow integrations.

  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
app/src/main/java/com/nextcloud/talk/dagger/modules/ManagerModule.kt (1)

39-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant @Provides binding or the @Inject constructor.

TelecomManager has an @Inject constructor and a @Singleton annotation. The @Provides method on Lines 39-41 duplicates this binding. Dagger can resolve the class directly, but it reports a duplicate binding only for explicit @Binds or @Provides pairs. Keep one source of truth to avoid confusion.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0d1211a3-74dc-44ad-ba17-5de474975924
📥 Commits

Reviewing files that changed from the base of the PR and between f3c9097 and ce3f084.

📒 Files selected for processing (8)
  • app/build.gradle.kts
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
  • app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt
  • app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
  • app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt
  • app/src/main/java/com/nextcloud/talk/dagger/modules/ManagerModule.kt
  • app/src/main/java/com/nextcloud/talk/services/CallForegroundService.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt Outdated
Comment thread app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt Outdated
Comment thread app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
Comment thread app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
Comment thread app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt Outdated
Comment thread app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt Outdated
@thecyberd3m0n
thecyberd3m0n marked this pull request as draft October 5, 2026 09:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 GitHub limitations.

⚠️ Outside diff range comments (1)

🟠 Major · Send leaveCall when Telecom rejects a valid incoming call. · CallNotificationActivity.kt:94-98

app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt:94-98
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Send leaveCall when Telecom rejects a valid incoming call.

onRejectCall currently calls only hangup(), which finishes the activity without calling NcApi.leaveCall. A valid incoming Talk call can remain active. Guard the nullable token instead of using roomToken!!. handleExtras() maps a missing key to "", so the assertion does not crash for that case, but it would build an invalid URL.

Suggested fix
+import io.reactivex.schedulers.Schedulers
+
...
             onRejectCall = {
                 runOnUiThread {
+                    val token = roomToken
+                    if (!token.isNullOrEmpty()) {
+                        val apiVersion = ApiUtils.getCallApiVersion(
+                            userBeingCalled,
+                            intArrayOf(ApiUtils.API_V4, 1)
+                        )
+                        val credentials = ApiUtils.getCredentials(
+                            userBeingCalled.username,
+                            userBeingCalled.token
+                        )
+                        ncApi!!.leaveCall(
+                            credentials,
+                            ApiUtils.getUrlForCall(apiVersion, userBeingCalled.baseUrl, token),
+                            null
+                        ).subscribeOn(Schedulers.io()).subscribe(
+                            { },
+                            { e -> Log.w(TAG, "Something went wrong when rejecting the call", e) }
+                        )
+                    }
                     hangup()
                 }
             }
🧹 Nitpick comments (1)
app/src/test/java/com/nextcloud/talk/call/TelecomManagerTest.kt (1)

82-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the add-call exception tests observe the launched coroutine.

The incoming and outgoing tests stub registerPhoneAccount, but those methods call CallsManager.addCall, which delegates to the platform TelecomManager.addCall overload. They also launch on TelecomManager’s independent CoroutineScope(Dispatchers.Main). The tests therefore do not reliably detect removal of the add-call catch blocks. Inject the test’s TestScope, make the four-argument addCall stub throw, and advance the test scheduler. This is a test-coverage gap, not evidence of a current production failure.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0b14f5a9-9d54-4d21-ae9f-0d2423ec7125
📥 Commits

Reviewing files that changed from the base of the PR and between ce3f084 and 472121f.

📒 Files selected for processing (2)
  • app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
  • app/src/test/java/com/nextcloud/talk/call/TelecomManagerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Signed-off-by: Dmitrij Rysanow <thecyberd3m0n@gmail.com>
@thecyberd3m0n
thecyberd3m0n marked this pull request as ready for review October 5, 2026 12:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f67d1f90-1548-4aa7-9615-4ca7a391c469
📥 Commits

Reviewing files that changed from the base of the PR and between 472121f and d7efa98.

📒 Files selected for processing (4)
  • app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
  • app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
  • app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt
  • app/src/test/java/com/nextcloud/talk/call/TelecomManagerTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
…gistration token for telecom

Signed-off-by: Dmitrij Rysanow <thecyberd3m0n@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 13653a15-c309-4932-9b3c-caaae5226be5
📥 Commits

Reviewing files that changed from the base of the PR and between d7efa98 and b5c5425.

📒 Files selected for processing (1)
  • app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/call/TelecomManager.kt
@mahibi

mahibi commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Thank you for contributing @thecyberd3m0n 👍
As we are currently behind with reviewing please understand that review might take a bit. I will try my best to have a close look soon.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Call registration, lifecycle cleanup, disconnect handoff, and advertised hold support have unresolved functional defects.

7 open findings
What changed in this PR

Integrates Talk calls with Android Telecom to support native call controls on compatible devices, including Wear OS.

Changes:

  • Adds a Telecom manager for call registration and control.
  • Connects call activities and foreground-service permissions to Telecom.
  • Adds manager tests and an Android-version navigation fallback.
File Description
app/​src/​test/​java/​com/​nextcloud/​talk/​call/​TelecomManagerTest.kt Adds Telecom manager tests.
app/​src/​main/​java/​com/​nextcloud/​talk/​services/​CallForegroundService.kt Adds phone-call foreground-service type.
app/​src/​main/​java/​com/​nextcloud/​talk/​dagger/​modules/​ManagerModule.kt Provides the singleton Telecom manager.
app/​src/​main/​java/​com/​nextcloud/​talk/​callnotification/​CallNotificationActivity.kt Registers incoming calls and answer/reject callbacks.
app/​src/​main/​java/​com/​nextcloud/​talk/​call/​TelecomManager.kt Implements Telecom registration and call controls.
app/​src/​main/​java/​com/​nextcloud/​talk/​application/​NextcloudTalkApplication.kt Registers the app with Telecom at startup.
app/​src/​main/​java/​com/​nextcloud/​talk/​activities/​CallActivity.kt Connects call lifecycle to Telecom and adjusts navigation fallback.
app/​src/​main/​AndroidManifest.xml Declares Telecom permissions and service type.
app/​build.gradle.kts Adds Core-Telecom dependency.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
Comment thread app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt Outdated
}
)
} else {
telecomManager.setCallActive(roomToken)
address = Uri.parse("talk:$roomToken"),
direction = CallAttributesCompat.DIRECTION_INCOMING,
callType = getCallType(isVideo),
callCapabilities = CallAttributesCompat.SUPPORTS_SET_INACTIVE
Comment thread app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt Outdated
Comment on lines +86 to +89
telecomManager.addIncomingCall(
displayName = displayName ?: "Nextcloud Talk Call",
roomToken = roomToken!!,
isVideo = isInCallWithVideo(callFlag),
Comment thread app/src/main/java/com/nextcloud/talk/callnotification/CallNotificationActivity.kt Outdated
@mahibi

mahibi commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Hi @thecyberd3m0n thanks for the PR.

As i dont have a WearOS for testing i hope some of my colleagues have one. If they don't we will test with the Android Studio Emulator.

Could you have a look at the comments from Copilot here in github and also these findings from Claude?:

  1. An incoming call that is declined or missed stays registered with Telecom. CallNotificationActivity.hangup(), the "notification gone, so finish()" routine, the E2EE early exit in proceedToCall() and onDestroy() never call           
     telecomManager.endCurrentCall(roomToken). If the user taps decline in the app, or the caller hangs up while it is ringing, the self-managed call keeps ringing as far as Telecom knows. Two follow-on effects:                          
     - prepareCallRegistration refuses every later registration while currentRoomToken != null, so Telecom stays dead until the process is killed.                                                                                           
     - If the user later joins that same room, hasActiveCall(room) returns true and setCallActive acts on the stale ringing call.                                                                                                            
                                                                                                                                                                                                                                             
     The same leak happens when the user accepts but CallActivity never reaches the joined state (the isCallSetUp == false path in onDestroy).                                                                                               
  2. System hang-up does nothing for a call that came in as incoming. The onDisconnect handler for an incoming registration calls CallNotificationActivity's onRejectCall. After the user accepts, that activity finishes within about a     
     second: onStop cancels the notification, and the cancel routine then calls finish(). So a hang-up from a Bluetooth device, the car, a watch, or "end and answer" for a cellular call hits the isFinishing/isDestroyed guard and does    
     nothing. Telecom state is cleared, but the WebRTC call keeps running. Only outgoing registrations call back into CallActivity.hangup().                                                                                                 
     - Fix: have CallActivity take over the disconnect handler when it attaches to an existing registration, for example a setOnDisconnect or a shared listener in TelecomManager.                                                           
  3. An incoming call is accepted with setActive() instead of answer(). When the user accepts in the app, CallActivity calls setCallActive, which calls scope.setActive(). The CallControlScope docs say: "For incoming calls that are ready 
     to be answered, use answer." On API < 34 (CallSessionLegacy) this happens to work. On the API 34+ transactional path, Telecom may reject it with an error and leave the call ringing. The return value is never checked, so the failure 
     would be silent. This needs a test on an Android 14+ device.                                                                                                                                                                            
     - Fix: TelecomManager should remember the direction and call answer(callType) for incoming calls.                                                                                                                                       
  4. Changed init order in NextcloudTalkApplication. componentApplication.inject(this) now runs before Security.insertProviderAt(Conscrypt.newProvider(), 1). Injection builds okHttpClient, so its TLS setup may be chosen before the       
     bundled Conscrypt is installed. That could drop TLS 1.3 on API 26–28. Only registerAppWithTelecom() needs to come after injection, so keep inject where it was and call registerAppWithTelecom() after it.                              
  5. Smaller issues                                                                                                                                                                                                                          
     - Callback return values: in 1.0.0, onAnswer, onDisconnect, onSetActive and onSetInactive are suspend (...) -> Unit. The CallControlResult.Success() values the lambdas return are thrown away, which is misleading.                    
     - Dead code: isLocallyDisconnecting is a guard that never fires. A disconnect the app starts itself doesn't trigger onDisconnect, and handleCallDisconnected has already cleared the registration ID beforehand anyway.                 
     - Threading: the state fields are changed from the main thread and from core-telecom callback coroutines, but they aren't confined to one thread or marked @Volatile.                                                                   
     - Duplicate DI binding: TelecomManager has both an @Inject @Singleton constructor and a @Provides @Singleton in ManagerModule. Keep one.                                                                                                
     - Hard-coded text: the "Nextcloud Talk Call" fallback is in two places and isn't a localized string resource.                                                                                                                           
     - Unrelated changes: the openConversationListInPrimaryTask restructuring (an API-Q guard, which looks like a lint fix) and qualifying Notification.FOREGROUND_SERVICE_IMMEDIATE don't belong to this feature. AGENTS.md asks for focused
       PRs, so consider splitting them out.                                                                                                                                                                                                  
  6. Tests: they cover TelecomManager's internal bookkeeping (duplicate registrations, mismatched rooms, the queued setActive). None covers findings 1–3, which are about how the two activities wire into it. I didn't run the tests or     
     build the branch.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Dmitrij Rysanow <thecyberd3m0n@gmail.com>
@thecyberd3m0n

Copy link
Copy Markdown
Author

During my work on this PR, I realized that tying OS-level telecom events to an Activity lifecycle creates very fragile edge cases. While this PR adds a scoped fix by forwarding the disconnect handler from CallNotificationActivity to CallActivity, I think we should strongly consider an architectural refactor.\n\nI just opened #6929 to propose extracting the WebRTC engine out of CallActivity and into a ForegroundService. Doing this would prevent slow call loading, dropped background calls, rotation glitches, and make managing these Telecom callbacks—and the ongoing Jetpack Compose migration—significantly cleaner in the future!

@thecyberd3m0n

thecyberd3m0n commented Oct 18, 2026 •

Copy link
Copy Markdown
Author

@mahibi I've resolved issues mentioned by Copilot, and deeply tested the issue with 2 phones and watch. All synchronization issues were resolved, and call state is in sync with Telecom/external devices. If you want - I can record a short video on how it works now

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.

Feature request: integrate with Android Telecom (ConnectionService) for native call handling

3 participants