cometchat-android-v5-calls-sdk
SkillWeb & browsingAdd voice & video calling to any Android app FROM SCRATCH with the headless CometChat Calls SDK v5 (`com.cometchat:calls-sdk-android:5.0.4`), no UI Kit. Kotlin: initFromSettings→login→generateToken→joinSession into a RelativeLayout, lifecycle-bound listeners, meet-style session rooms AND 1:1 ringing (Chat SDK v5 signaling + Calls SDK media), call logs, layouts, audio modes, recording, PiP, background service. Triggers: 'add calling from scratch android', 'standalone video call kotlin', 'headless calls sdk android', 'build my own call UI android', 'meeting room join by session id android', 'one-on-one ringing call without uikit android'.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the cometchat-android-v5-calls-sdk skill
What this skill tells your AI
The instructions your AI receives, as published by cometchat/cometchat-skills in skills/cometchat-android-v5-calls-sdk/SKILL.md and read by ahel’s review.
Ground truth:
com.cometchat:calls-sdk-android:5.0.4+ catalogandroid-calls-v5.json(the CLOSED list,javap-verified on the installed AAR). Docs:/calls/android/**= v5 · Docs MCP. Fetch builder options, callback lists and filters from the docs (references/docs-map.md) — never memory. ⚠️ Where docs and AAR disagree, the AAR wins (drift list inreferences/pitfalls.md). APPEND to the user's app — additive wiring only (RULES.md). HEADLESS path (no UI Kit); for the prebuilt call UI usecometchat-android-v6-calls.
Companion skills (read first)
- Standalone/headless entry — owns its own artifact (the Calls SDK), no
-coresibling. - 1:1 RINGING only also drives the Chat SDK (
com.cometchat:chat-sdk-android:5.0.5) for signaling —references/ringing.md; deeper Chat-SDK usage:cometchat-android-v5-sdk. - Prefer the UI Kit? Chat + a prebuilt call UI ⇒
cometchat-android-v6-core+cometchat-android-v6-calls, not this skill.
Use this skill when
"add calling from scratch / without the UI Kit", "standalone voice/video on Android", "build my own call screen", "headless calls SDK", "meeting room join by session id", "one-on-one ringing call". Precondition: the caller chose the from-scratch / standalone path (prebuilt call UI ⇒ see Companion skills).
Prerequisites & install
// settings.gradle(.kts) → dependencyResolutionManagement.repositories
maven("https://dl.cloudsmith.io/public/cometchat/cometchat/maven/")
// app/build.gradle.kts
implementation("com.cometchat:calls-sdk-android:5.0.4") // meet-style needs ONLY this (+ the two RN lines below)
implementation("com.cometchat:chat-sdk-android:5.0.5") // + this for 1:1 RINGING (signaling)
implementation("com.facebook.react:react-android:0.77.2") // the SDK's embedded RN runtime — see "SoLoader" below
implementation("com.facebook.soloader:soloader:0.12.1") // (versions = what calls-sdk-android 5.0.4 pulls)
# gradle.properties — REQUIRED for the 1:1 RINGING path (chat-sdk-android 5.0.5 still pulls android.arch.lifecycle:extensions:1.1.1 → support-compat 26.1.0)
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m # REQUIRED: Jetifier transforms the embedded react-android AAR — the AS-default -Xmx2048m OOMs (AUDIT-225)
<!-- AndroidManifest.xml, inside <application> — REQUIRED for background handling: the AAR declares NO <service> -->
<service android:name="com.cometchat.calls.services.CometChatOngoingCallService"
android:exported="false"
android:foregroundServiceType="camera|microphone|mediaPlayback" />
Full error-text evidence for the four build gates below (minSdk,
<service>, Jetifier, heap):references/build-truth.md.
- minSdk 26 — the manifest merge is the floor.
calls-sdk-android:5.0.4pullsreact-native-svgatminSdkVersion 26; a 24/25 app failsprocess*MainManifest. Docs now agree (AUDIT-229) — raiseminSdkfirst. - The
<service>above is REQUIRED. The 5.0.4 AAR merges permissions only; without YOUR declarationCometChatOngoingCallService.launch(activity)fails silently (call dies on Home). Docs ship the<service>block (corrected upstream, verified 2026-09-09). - Jetifier (ringing only). Without
android.enableJetifier=true, an AndroidX app +chat-sdk-androidfailscheckDebugDuplicateClasses. Meet-style needs none. - ⚠️ Gradle heap
-Xmx4096m(MANDATORY — the FIRST build OOMs, meet-style too, AUDIT-225). The AS-default-Xmx2048mOOMs Jetifier transforming the embeddedreact-androidAAR; thegradle.propertiesline above fixes it. - ⚠️ SoLoader (MANDATORY on 5.0.x; a REQUIRED skill step — the SDK doesn't self-init on the
joinSessionpath). The call surface is an embedded React Native 0.77; without the merged-mapping initjoinSessioncrashes the process (UnsatisfiedLinkError: … "libhermes_executor.so" not found). Add ONCE, first inApplication.onCreate()(hence the two RN artifacts on YOUR compile classpath):import com.cometchat.calls.core.CometChatCalls import com.facebook.react.soloader.OpenSourceMergedSoMapping import com.facebook.soloader.SoLoader SoLoader.init(this, OpenSourceMergedSoMapping) // Application.onCreate(), BEFORE CometChatCalls.initFromSettings — idempotent - Manifest: the AAR merges the call permissions (INTERNET, CAMERA, RECORD_AUDIO, FOREGROUND_SERVICE_*, POST_NOTIFICATIONS, …) — but NOT the
<service>above. Java 11compileOptions. - Runtime permissions are YOURS to request (API 23+):
CAMERA+RECORD_AUDIO(video) /RECORD_AUDIO(voice) beforejoinSession. Denied ⇒ tell the user; never join silently muted. - Credentials: App ID · Region · Auth Key (dev only) live in the gitignored
app/src/main/assets/cometchat-settings.json(same file the chat core uses; shape →references/docs-map.md§ DOCS-GAP). Fetch via the CLI —npx @cometchat/skills-cli@3 auth login→provision use --app-id <id> --json(@3pins the CLI major that matches the v5 skills) — or paste from Dashboard → Credentials. Mint auth tokens server-side for production; never ship the Auth Key.
Init & login ordering (BAKED — invariant)
CometChatCalls.initFromSettings(context, listener) resolves (onSuccess) → CometChatCalls.login(uid, authKey, …) (dev) or login(authToken, …) (prod) resolves → then generateToken / joinSession / listeners. Nothing joins before init+login resolve; before init every call fails with ERROR_COMETCHAT_CALLS_SDK_INIT.
- DEFAULT to
initFromSettings— the ai-agent/telemetry-attributed path (integrationSource="ai-agent", parallel toCometChat.initFromSettings; RULES §5, AUDIT-175). It readsassets/cometchat-settings.json(appId,region, optionalcallsSDKhosts). Intentionally undocumented (@nodoc); the documentedCometChatCalls.init(context, CallAppSettings.CallAppSettingBuilder(...).build(), listener)is the FALLBACK only. authKeyis read back out of the same asset (credentials.authKey) — never a second copy. Skiploginonly whengetLoggedInUser()?.uid == uid— a DIFFERENT uid ⇒ logout → RE-INIT → login (references/user-switch.md);!= nullalone calls as the PREVIOUS user. Init ONCE, inApplication.onCreate().
import com.cometchat.calls.core.CometChatCalls
import com.cometchat.calls.exceptions.CometChatException
import com.cometchat.calls.model.CallUser
import org.json.JSONObject
// Call from Application.onCreate(); it re-enters itself after a user-switch logout.
fun initThenLogin(uid: String) {
CometChatCalls.initFromSettings(this, object : CometChatCalls.CallbackListener<String>() { // telemetry default; init(context, CallAppSettings, …) is the doc fallback
override fun onSuccess(result: String) {
val current = CometChatCalls.getLoggedInUser()
if (current?.uid == uid) return // SAME user — already logged in
if (current != null) { // DIFFERENT user — NOT `!= null` → login (that calls as the PREVIOUS user)
CometChatCalls.logout(object : CometChatCalls.CallbackListener<String>() {
// logout leaves isInitialized()==false → RE-INIT before login (ringing: CometChat.logout too — references/user-switch.md)
override fun onSuccess(msg: String) = initThenLogin(uid)
override fun onError(e: CometChatException) { /* surface e.code — never continue as the old user */ }
})
return
}
// dev: login(uid, authKey, l) — authKey from the SAME asset; prod: login(authToken, l), token minted server-side.
val authKey = JSONObject(assets.open("cometchat-settings.json").bufferedReader().readText())
.getJSONObject("credentials").getString("authKey")
CometChatCalls.login(uid, authKey, object : CometChatCalls.CallbackListener<CallUser>() {
override fun onSuccess(callUser: CallUser) { /* unlock calling */ }
override fun onError(e: CometChatException) { /* surface e.code + e.message */ }
})
}
override fun onError(e: CometChatException) { /* ERROR_SETTINGS_FILE_NOT_FOUND ⇒ the asset is missing */ }
})
}
initThenLogin(uid)
Two build modes (BAKED — the router picks one)
- Meet-style (session room) — Calls SDK ONLY:
generateToken(sessionId)→joinSession(token, sessionSettings, relativeLayout, …)(or thejoinSession(sessionId, …)overload); everyone on the samesessionIdshares the call. No ringing. - 1:1 ringing — Chat SDK signals, Calls SDK carries media (§ 1:1 ringing below — FOREGROUND-only without VoIP push).
SDK method map (BAKED closed list — from the catalog; signatures → FETCH from docs)
- Lifecycle (
CometChatCallsstatics):initFromSettings·init·isInitialized·login(uid, authKey, l)/login(authToken, l)·logout·getLoggedInUser·getUserAuthToken - Session:
generateToken(sessionId, l)→GenerateToken.token·joinSession(token | sessionId, SessionSettings, RelativeLayout, l)→CallSession(startSession/CallSettingsBuilder= v4 path, do not use) · builderCometChatCalls.SessionSettingsBuilder()(setSessionType/setLayout/setAudioMode/…, full list in the reference). - Active call (
CallSession.getInstance()):isSessionActive·leaveSession· lifecycle-bound listeners (addSessionStatusListener(owner, …)+ siblings) ·clearAllListeners. - Also (full enumeration →
references/method-map.md): In-call actions (CUSTOM controls only) · Call logs (CallLogRequest) · Transcription & closed captions (v5.0.4+, plan-gated) · Background (CometChatOngoingCallService) · Constants/enums (SessionType/LayoutType/AudioMode/CometChatCallsConstants.*).
A curated highlight, NOT the full surface — the AUTHORITATIVE closed list is the
android-calls-v5.jsoncatalog (also transcription/streaming,CometChatCallsEventsListener, the v4-eraCallSettingsBuilder). A symbol is real iff it's in the catalog; then fetch its page viareferences/docs-map.md. Full method map:references/method-map.md.
Listener lifecycle (BAKED)
Every CallSession listener takes a LifecycleOwner and is removed with it — register from joinSession's onSuccess with the Activity/Fragment as owner; no manual remove (clearAllListeners() for hard resets). Still yours by hand: CallSession.getInstance().leaveSession() on teardown, and for ringing CometChat.removeCallListener(ID).
Least-code recipe (meet-style, Kotlin Activity)
import android.Manifest
import android.os.Bundle
import android.view.ViewGroup
import android.widget.RelativeLayout
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.cometchat.calls.core.CallSession
import com.cometchat.calls.core.CometChatCalls
import com.cometchat.calls.exceptions.CometChatException
import com.cometchat.calls.listeners.SessionStatusListener
import com.cometchat.calls.model.GenerateToken
import com.cometchat.calls.model.LayoutType
import com.cometchat.calls.model.SessionType
class MeetCallActivity : AppCompatActivity() { // init + login already resolved (Application)
private lateinit var callContainer: RelativeLayout
private val permissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { granted ->
if (granted.values.all { it }) joinRoom(intent.getStringExtra("SESSION_ID") ?: "team-room")
else finish() // tell the user — a call without mic/camera cannot start
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// The SDK renders its ENTIRE call UI into this view — a RelativeLayout with REAL size. No external controls.
callContainer = RelativeLayout(this).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
setContentView(callContainer)
permissions.launch(arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))
}
private fun joinRoom(sessionId: String) {
val sessionSettings = CometChatCalls.SessionSettingsBuilder() // fetch more options from /calls/android/session-settings
.setSessionType(SessionType.VIDEO) // ⚠️ setSessionType — the docs' setType() does not exist here
.setLayout(LayoutType.TILE)
.setDisplayName("Me")
.setIdleTimeoutPeriod(120) // alone for 2 min ⇒ onSessionTimedOut
.build()
CometChatCalls.generateToken(sessionId, object : CometChatCalls.CallbackListener<GenerateToken>() {
override fun onSuccess(token: GenerateToken) {
CometChatCalls.joinSession(token, sessionSettings, callContainer, object : CometChatCalls.CallbackListener<CallSession>() {
override fun onSuccess(callSession: CallSession) {
// Listeners are LIFECYCLE-BOUND (owner = this Activity) — register here, after join.
callSession.addSessionStatusListener(this@MeetCallActivity, object : SessionStatusListener() {
override fun onSessionLeft() { finish() }
override fun onSessionTimedOut() { finish() }
override fun onConnectionClosed() { finish() }
})
}
override fun onError(e: CometChatException) { /* show e.code + e.message, then finish() */ }
})
}
override fun onError(e: CometChatException) { /* ERROR_AUTH_TOKEN ⇒ not logged in; show it */ }
})
}
override fun onDestroy() {
if (CallSession.getInstance().isSessionActive) CallSession.getInstance().leaveSession() // never leak a live session
// If you called CometChatOngoingCallService.launch(...), ALSO CometChatOngoingCallService.abort(this) here —
// onSessionLeft is NOT delivered on Activity destroy (BACK), so callback-only abort leaks the FGS (AUDIT-227).
super.onDestroy()
}
}
Compose host: the same code inside AndroidView(factory = { RelativeLayout(it) }) filling Modifier.fillMaxSize(); declare the Activity with configChanges (Rotation pitfall, references/pitfalls.md).
1:1 ringing (Chat SDK signaling + Calls SDK media)
Register the CallListener once, globally (Application.onCreate, after CometChat.initFromSettings + CometChat.login — the cometchat-android-v5-sdk recipe) so the callee rings from any screen of a FOREGROUND app. Full recipe (both roles, Kotlin) + the ringing prerequisites: references/ringing.md — initiateCall → peer onIncomingCallReceived → acceptCall/rejectCall → joinSession(call.sessionId) on BOTH sides → hang-up = leaveSession and CometChat.endCall.
- Foreground-only (verified live). In auto socket mode the Chat SDK drops its WebSocket the instant the app is backgrounded — a backgrounded callee never gets
onIncomingCallReceivedand a backgrounded caller never getsonOutgoingCallAccepted(not replayed on return). Background / killed-app ringing = the VoIP push path (/calls/android/voip-calling). Never promise it from this recipe. Full evidence + the caller-side reconcile (dismiss on timeout,onResumere-check):references/ringing.md.
Common pitfalls (BAKED)
One-liners here; full evidence + reasoning for every one — see
references/pitfalls.md. minSdk-26 floor + missing<service>are in Prerequisites (evidence:references/build-truth.md).
- Don't duplicate the built-in call controls (the #1 mistake).
joinSessionrenders a COMPLETE call UI (mute, camera, switch, audio mode, layout, participants, raise hand, recording, invite, timer, red leave) into yourRelativeLayout— no custom control row unless the ask is "replace the controls" (thenhideControlPanel(true)FIRST). - Docs ≠ AAR (verified vs 5.0.4):
setType→setSessionType·unMuteAudio→unmuteAudio·unPinParticipant→unpinParticipant·pinParticipant(uid)→(uid, pid)·MediaEventsListener.onScreenShareStarted/Stoppeddon't exist (they're onParticipantEventListener) ·raiseHand/setLayout/startRecordingare no-ops. Per-page notes:references/docs-map.md. - Wrong container —
joinSessionneeds a real-sizedRelativeLayout;wrap_content/zero size renders invisibly (ERROR_CALLING_VIEW_REF_NULLif null). joinSessioncrasheslibhermes_executor.so not found— SoLoader merged-mapping init missing; see Prerequisites § SoLoader.- Joining before init+login resolve —
ERROR_COMETCHAT_CALLS_SDK_INIT/ERROR_AUTH_TOKEN; chain off the callbacks. ERROR_COMETCHAT_CALLS_SDK_INITafter a user switch is MISLEADING (AUDIT-194) — re-init between logout and login:references/user-switch.md.- No runtime permissions — the call joins with a dead mic/camera; request
CAMERA+RECORD_AUDIOfirst. - Listeners registered before join — register in
joinSession'sonSuccesswith aLifecycleOwner. - Hang-up on one SDK only (ringing) —
leaveSessionwithoutCometChat.endCallleaves the peer in-call; do both. - Android can't START screen share (receive-only), and has no virtual-background page — don't promise either.
- Rotation / Home kill the call —
configChanges(+supportsPictureInPicture); keep alive withCometChatOngoingCallService.launch(activity)(needs the<service>). - Ongoing-call service LEAKS if you only
.abortinonSessionLeft(AUDIT-227) — alsoCometChatOngoingCallService.abort(this)inonDestroy; abort on EVERY teardown path. - No-op in-call actions (5.0.4 SDK bug, AUDIT-187):
raiseHand/lowerHand, mid-callsetLayout,startRecording/stopRecording— no effect/callback/error; prefer the SDK's own buttons. - Backgrounded apps do not ring and do not hear the accept — foreground only; VoIP push for the rest (§ 1:1 ringing).
- Mixing majors — Calls SDK is 5.x (
joinSession);startSession/CallSettingsBuilder+/calls/v4/androidare the OLD tree.
Verify it works
- Tier-1 catalog: every emitted symbol is in
android-calls-v5.json(node test-suite/scripts/verify-catalog.mjs). - Tier-2 fences: the Kotlin compiles against the installed
calls-sdk-android:5.0.4(npm run verify:fences:android-v6); your app builds atminSdk 26(the manifest merge is the floor check). - Tier-3 device:
./gradlew :app:assembleDebug(atminSdk 26), then on an emulator/device: grant mic+camera →joinSessionshows the SDK call surface with its own controls → a second device on the samesessionIdappears as a tile → Home shows the ongoing-call notification (the<service>works) → leave ends cleanly (onSessionLeft). Ringing: two devices/users, BOTH apps FOREGROUND — accept joins both, hang-up ends both; then background the callee and confirm your outgoing UI times out cleanly (no callback comes). The pack's reviewer proves the round-trip viaCallsSmokeTests.
Signals
- GitHub stars
- 109
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
cometchat-android-v5-calls-sdk- Source
- github.com/cometchat/cometchat-skills