diff --git a/calls/android/events.mdx b/calls/android/events.mdx index 7f950ffcf..b596cbc0e 100644 --- a/calls/android/events.mdx +++ b/calls/android/events.mdx @@ -5,7 +5,7 @@ sdk_version: "5.x" description: "Handle CometChat Calls SDK v5 events on Android for call lifecycle, participants, media state, errors, and real-time updates." --- -Handle call session events to build responsive UIs. The SDK provides five session listener interfaces to monitor session status, participant activities, media changes, button clicks, and layout changes, plus two global listeners for incoming calls and login state. Each listener is lifecycle-aware and automatically cleaned up when the Activity or Fragment is destroyed. +Handle call session events to build responsive UIs. The SDK provides five session listener interfaces to monitor session status, participant activities, media changes, button clicks, and layout changes, plus two global listeners for incoming calls and login state. The five session listeners are lifecycle-aware and automatically cleaned up when the Activity or Fragment is destroyed; the two global listeners are not, so remove those yourself. ## Get CallSession Instance @@ -24,10 +24,10 @@ CallSession callSession = CometChatCalls.getActiveSession(); // null when no s -`getActiveSession()` returns the session as soon as it is **prepared**, which is what `CometChatCalls.initiateCall` does on the caller and what the Push SDK's ring does on the callee, before anyone joins. That is what lets a caller listen for "ringing" and "answered" without joining (see [Ringing](/calls/android/ringing)). `CallSession.getInstance()` still works and returns the same object. +`getActiveSession()` returns the session as soon as it is **prepared**, which is what `CometChatCalls.initiateCall` does on the caller and what the Push SDK's ring does on the callee, before anyone joins. That is what lets a caller listen for "ringing" and "answered" without joining (see [Ringing](/calls/android/ringing)). Before a session is prepared or joined, `getActiveSession()` returns null, so a listener added through it is skipped; the samples below use `callSession?.` for that reason. Add listeners once `joinSession` (or a prepare) has succeeded, or register them earlier through `CallSession.getInstance()`, which returns the same object. -All event listeners are lifecycle-aware and automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed. You don't need to manually remove listeners. +All session event listeners are lifecycle-aware and automatically removed when the `LifecycleOwner` (Activity/Fragment) is destroyed. You don't need to manually remove listeners. --- @@ -39,7 +39,7 @@ Monitor the call session lifecycle including join/leave events and connection st ```kotlin -callSession.addSessionStatusListener(this, object : SessionStatusListener() { +callSession?.addSessionStatusListener(this, object : SessionStatusListener() { override fun onSessionJoined() { // Successfully connected to the session } @@ -112,10 +112,10 @@ callSession.addSessionStatusListener(this, new SessionStatusListener() { |-------|-------------| | `onSessionJoined()` | Successfully connected and joined the session | | `onSessionLeft()` | Left the session via `leave()` or was removed | -| `onSessionTimedOut()` | Session ended due to inactivity timeout. On a prepared session: nobody joined in time | +| `onSessionTimedOut()` | Session ended due to inactivity timeout. Not used on a prepared session: one nobody joins within 5 minutes reports `onConnectionClosed` | | `onConnectionLost()` | Network interrupted, SDK attempting reconnection | | `onConnectionRestored()` | Connection restored after being lost | -| `onConnectionClosed()` | Connection permanently closed, cannot reconnect. On a prepared session: the call ended before anyone joined (declined, cancelled from another device, or the room went away); the reason arrives through `onIncomingCallEnded` | +| `onConnectionClosed()` | Connection permanently closed, cannot reconnect. On a prepared session: the call ended before anyone joined (declined, cancelled from another device, the room went away, or nobody joined within 5 minutes); the reason arrives through `onIncomingCallEnded` | --- @@ -126,7 +126,7 @@ Monitor participant activities including join/leave, audio/video state, hand rai ```kotlin -callSession.addParticipantEventListener(this, object : ParticipantEventListener() { +callSession?.addParticipantEventListener(this, object : ParticipantEventListener() { override fun onParticipantConnected(participant: Participant) { // A participant's device prepared the session but has not joined yet. // On an outgoing call: the callee's phone is ringing. @@ -215,7 +215,7 @@ Monitor your local media state changes including audio/video status, recording, ```kotlin -callSession.addMediaEventsListener(this, object : MediaEventsListener() { +callSession?.addMediaEventsListener(this, object : MediaEventsListener() { override fun onAudioMuted() { // Your microphone was muted } @@ -347,7 +347,7 @@ Intercept UI button clicks from the default call interface to add custom behavio ```kotlin -callSession.addButtonClickListener(this, object : ButtonClickListener() { +callSession?.addButtonClickListener(this, object : ButtonClickListener() { override fun onLeaveSessionButtonClicked() { // Leave button tapped } @@ -420,7 +420,7 @@ Monitor layout changes including layout type switches and Picture-in-Picture mod ```kotlin -callSession.addLayoutListener(this, object : LayoutListener() { +callSession?.addLayoutListener(this, object : LayoutListener() { override fun onCallLayoutChanged(layoutType: LayoutType) { // Layout changed (TILE, SPOTLIGHT) } @@ -534,7 +534,7 @@ CometChatCalls.removeIncomingCallListener("my-screen"); | Event | Parameters | Description | |-------|------------|-------------| | `onIncomingCallReceived` | `InitiatedCall` | A call for the logged-in user is ringing | -| `onIncomingCallEnded` | `InitiatedCall`, `reason` | The call ended before being answered here. Caller: `rejected`, `busy`, `unanswered`, `cancelled`. Callee still ringing: `cancelled`, `ongoing` (answered elsewhere), `rejected` / `busy` (declined here), `unanswered` | +| `onIncomingCallEnded` | `InitiatedCall`, `reason` | The call ended before being answered here. Caller: `rejected`, `busy`, `unanswered`, `cancelled`. Callee still ringing: `cancelled`, `ongoing` (answered elsewhere), `rejected` / `busy` (declined here), `unanswered`, `ended` (the prepared session closed before the answer). Treat any other reason as "the ring is over" | `InitiatedCall` carries `sessionId`, `messageId`, `conversationId`, `sender`, `senderName`, `senderAvatar`, `receiver`, `receiverType`, `type`, `status` and `initiatedAt`. diff --git a/calls/android/migrating-from-legacy-ringing.mdx b/calls/android/migrating-from-legacy-ringing.mdx index 5fac668ed..c8a5e8dc2 100644 --- a/calls/android/migrating-from-legacy-ringing.mdx +++ b/calls/android/migrating-from-legacy-ringing.mdx @@ -167,17 +167,17 @@ Everything in this list is now done by the Push SDK. Remove it, or it will fight - The incoming-call overlay, snackbar, dialog or activity you launched from it - Your own ringtone / sound manager for incoming calls - The "already in a call → reject with busy" check -- `CometChatVoIP`, `PhoneAccount` registration, `CometChatVoIPConnectionService`, `CallConnection`, and the `READ_PHONE_STATE`, `MANAGE_OWN_CALLS` and `ANSWER_PHONE_CALLS` permission chain +- `CometChatVoIP`, `PhoneAccount` registration, `CometChatVoIPConnectionService`, `CallConnection`, and your own `READ_PHONE_STATE` and `ANSWER_PHONE_CALLS` requests. (`MANAGE_OWN_CALLS` still merges in from the Calls SDK's manifest; leave it.) - Any `isAppInForeground()` gate around call pushes -Keep: `RECORD_AUDIO`, `CAMERA` and `POST_NOTIFICATIONS` in the manifest. The SDKs request them but do not declare them. +`RECORD_AUDIO`, `CAMERA` and `POST_NOTIFICATIONS` merge into your manifest from the Calls SDK and the Push SDK's calls module, so you can remove your own declarations; keep requesting them at runtime. ## Step 6: Rewrite the call code | Legacy | Now | Notes | |--------|-----|-------| | `CometChat.initiateCall(Call(uid, RECEIVER_TYPE_USER, type), listener)` | `CometChatCalls.initiateCall(context, uid, InitiateCallOptions, listener)` | Set `options.sessionSettings` with the session type, or an audio call opens the camera | -| `CometChat.initiateCall(call, timeoutSeconds, listener)` | No client timeout. The ring window is 45 s on both sides (or the push's `ringTimeout`) | Run your own 45 s timer on the caller if you want the screen to close on its own | +| `CometChat.initiateCall(call, timeoutSeconds, listener)` | No client timeout. The callee's device rings for 45 s (or the push's `ringTimeout`) and then ends the call as `unanswered` | Run your own 45 s timer on the caller: if the callee's device is never reached, nothing else ends the caller's ringing for 5 minutes | | `CallListener.onIncomingCallReceived(call)` | Nothing to write; the Push SDK rings. For your own state use `IncomingCallListener.onIncomingCallReceived(InitiatedCall)` | One listener whether the call came by push on a killed process or on a live one | | `CallListener.onOutgoingCallAccepted(call)` | `ParticipantEventListener.onParticipantJoined(participant)` on the session `initiateCall` prepared, via `CometChatCalls.getActiveSession()` | New in-between event: `onParticipantConnected` = the callee's device is ringing | | `CallListener.onOutgoingCallRejected(call)` | `IncomingCallListener.onIncomingCallEnded(call, "rejected")` or `"busy"` | | @@ -190,7 +190,7 @@ Keep: `RECORD_AUDIO`, `CAMERA` and `POST_NOTIFICATIONS` in the manifest. The SDK | `CallSession.leaveSession()` + `CometChat.endCall(sessionId, listener)` | `CallSession.leave()` | No status to send on a hang-up; the room tells the other side and the backend logs it | | `CometChat.getActiveCall()` / `clearActiveCall()` | `CometChatCalls.getActiveSession()` (null when no session is prepared or joined) | | | `CometChat.startCall(...)`, `CallSettings`, `OngoingCallListener` | `joinSession` + the 5.x listeners (see the [v5 migration guide](/calls/android/migration-guide-v5)) | | -| `CometChatCallsConstants.*` | `com.cometchat.calls.constants.Constants.*` | The whole `CometChatCallsConstants` class is deprecated in 5.1 | +| `CometChatCallsConstants.*` | `com.cometchat.calls.constants.Constants.*` | The whole `CometChatCallsConstants` class has been deprecated since 5.0 | | `CometChat.logout(...)` | `CometChatCalls.logout(...)` as well (the UI Kit's `CometChatUIKit.logout` does both) | Otherwise the Calls SDK stays signed in as the old user and a ring is not stopped | ### Caller screen, before and after @@ -291,9 +291,9 @@ Details on the [Push ringing](/calls/android/voip-calling) page. ## What users will notice -- Calls ring with the app killed, from the lock screen, without Telecom permissions. +- Calls ring with the app killed, from the lock screen, without a `ConnectionService`. - The caller sees "Ringing…" once the callee's phone is reached, and the reason when a call is declined or missed. -- Both sides stop ringing after 45 seconds. +- The callee stops ringing after 45 seconds and the caller's screen closes with it (and on its own timer, if the callee's device was never reached). - Missed calls appear as notifications on the callee. - A call during a call is call waiting (answer ends the current call, decline sends busy), not an automatic reject. - The microphone and camera prompt appears on Answer the first time, on the device that answers. diff --git a/calls/android/migration-guide-v5.mdx b/calls/android/migration-guide-v5.mdx index 7970bcb72..e971dc17a 100644 --- a/calls/android/migration-guide-v5.mdx +++ b/calls/android/migration-guide-v5.mdx @@ -474,7 +474,7 @@ These classes still exist in v5 for backward compatibility but are deprecated: | Deprecated in 5.1 | Replacement | |-------------------|-------------| | `CallSession.leaveSession()` | `CallSession.leave()` | -| `CometChatCallsConstants` (the whole class) | `com.cometchat.calls.constants.Constants` | +| `CometChatCallsConstants` (the whole class; already deprecated since 5.0) | `com.cometchat.calls.constants.Constants` | | Chat SDK calling (superseded; deprecated in a coming Chat SDK release): `CometChat.initiateCall`, `acceptCall`, `rejectCall`, `endCall`, `startCall`, `getActiveCall`, `clearActiveCall`, `addCallListener`, `CallListener`, `CallSettings` | `CometChatCalls.initiateCall`, `endCall(sessionId, status)`, `addIncomingCallListener`, the Push Notifications SDK calls module, `leave()` | New in 5.1: `CometChatCalls.initiateCall`, `endCall(sessionId, status)`, `connect`, `getActiveSession`, `addIncomingCallListener` / `IncomingCallListener`, `addLoginListener` / `LoginListener`, `ParticipantEventListener.onParticipantConnected`. diff --git a/calls/android/ringing.mdx b/calls/android/ringing.mdx index 9983fc1d2..dbdf2708e 100644 --- a/calls/android/ringing.mdx +++ b/calls/android/ringing.mdx @@ -56,7 +56,7 @@ What each side sees: - [Calls SDK set up](/calls/android/setup) and the user [logged in](/calls/android/authentication). - Push notifications configured with the Push Notifications SDK 2.0 calls module. The whole push side of ringing is on the [Push ringing](/calls/android/voip-calling) page; this page assumes it is done. -- `RECORD_AUDIO` and `CAMERA` declared in your manifest. The SDKs request them at runtime but do not declare them. +- `RECORD_AUDIO` and `CAMERA` granted at runtime. The Calls SDK's manifest already declares them, so there is nothing to add to yours. - `minSdk 26`. ```kotlin build.gradle.kts @@ -133,7 +133,7 @@ CometChatCalls.initiateCall(context, receiverUid, options, |-----------|-------------| | `receiverUid` | UID of the user to call | | `options.type` | `Constants.SESSION_TYPE_AUDIO` or `Constants.SESSION_TYPE_VIDEO` | -| `options.receiverType` | `Constants.RECEIVER_TYPE_USER` | +| `options.receiverType` | `Constants.RECEIVER_TYPE_USER`, or `Constants.RECEIVER_TYPE_GROUP` with the group's GUID as `receiverUid` (see [Group calls](#group-calls)) | | `options.sessionSettings` | The settings the caller's session is prepared with. See the warning below. | `InitiatedCall` gives you `sessionId`, `messageId` (the id of the call message in the conversation), `sender`, `senderName`, `senderAvatar`, `receiver`, `receiverType`, `type`, `status`, `initiatedAt` and `conversationId`. @@ -143,7 +143,7 @@ CometChatCalls.initiateCall(context, receiverUid, options, -There is no client-side ring timeout. Both sides stop at the ring window the backend sends in the push (45 seconds by default). A caller that wants its own screen to time out can end the call as `unanswered` after the same window, as shown under [Ring timeout](#ring-timeout). +There is no client-side ring timeout. The callee's device stops at the ring window the backend sends in the push (45 seconds by default) and ends the call as `unanswered`, which closes the caller's call too. If the callee's device is never reached, nothing ends the caller's ringing until its prepared session expires after 5 minutes, so run your own timer on the caller and end the call as `unanswered` after the same window, as shown under [Ring timeout](#ring-timeout). ## Show "Ringing…" and detect the answer @@ -175,7 +175,6 @@ fun listenOnPreparedSession(call: InitiatedCall) { // The prepared session closed before anyone joined: the call is over. // The reason (rejected, busy, …) arrives through onIncomingCallEnded, usually first. override fun onConnectionClosed() = closeOutgoingScreen() - override fun onSessionTimedOut() = closeOutgoingScreen() }) } ``` @@ -208,7 +207,6 @@ void listenOnPreparedSession(InitiatedCall call) { // The prepared session closed before anyone joined: the call is over. // The reason (rejected, busy, …) arrives through onIncomingCallEnded, usually first. @Override public void onConnectionClosed() { closeOutgoingScreen(); } - @Override public void onSessionTimedOut() { closeOutgoingScreen(); } }); } ``` @@ -219,7 +217,7 @@ void listenOnPreparedSession(InitiatedCall call) { |-------|----------------------------------| | `onParticipantConnected(callee)` | The callee's phone got the push: it is ringing there | | `onParticipantJoined(callee)` | The callee answered. Call `joinSession` now | -| `onConnectionClosed` / `onSessionTimedOut` | The call ended before anyone joined (declined, cancelled from another device, or the session died) | +| `onConnectionClosed` | The call ended before anyone joined (declined, cancelled from another device, the session died, or the prepared session expired after 5 minutes). `onSessionTimedOut` is not part of this: it only reports an idle joined session | Both listeners are lifecycle-aware: pass your activity or fragment as the `LifecycleOwner` and the SDK removes them when it is destroyed. @@ -273,7 +271,7 @@ CometChatCalls.removeIncomingCallListener("my-screen"); | Who | `onIncomingCallEnded` reason | |-----|------------------------------| | Caller | `rejected` (callee declined), `busy` (callee was in another call), `unanswered` (nobody answered), `cancelled` (you cancelled from another device). Never `ongoing` or `ended`. | -| Callee, still ringing | `cancelled` (caller gave up), `ongoing` (answered on another device), `rejected` / `busy` (declined here), `unanswered` (ring timed out) | +| Callee, still ringing | `cancelled` (caller gave up), `ongoing` (answered on another device), `rejected` / `busy` (declined here), `unanswered` (ring timed out), `ended` (the prepared session closed before the answer; it fires once per call, so a later `cancelled` push is not reported). Treat any other reason as "the ring is over" | | Callee, answered | Nothing; the joined session reports the end through `onSessionLeft` / `onConnectionClosed` | @@ -282,7 +280,7 @@ Listener ids are keys. Registering the same id twice replaces the first listener ## Answer a call -The Push SDK rings and, on **Answer**, asks for the microphone (and the camera for a video call), then starts the activity you registered with `PNConfiguration.setAnswerActivity`. That activity reads the call from its intent and joins. There is no separate "accept" call to make. +The Push SDK rings and, on **Answer**, asks for the microphone (and the camera for a video call), then starts the activity you registered with `PNConfiguration.Builder.setAnswerActivity`. That activity reads the call from its intent and joins. There is no separate "accept" call to make. @@ -323,7 +321,7 @@ public class CallScreenActivity extends AppCompatActivity { ## Join the session -Both sides join the same way: the callee from the answer activity, the caller from `onParticipantJoined`. `joinSession` takes over the session that was prepared earlier, so it connects almost at once. +Both sides join the same way: the callee from the answer activity, the caller from `onParticipantJoined`. `joinSession` takes over the session that was prepared earlier, so it connects almost at once. On a prepared session the settings passed to `joinSession` are ignored: the ones frozen when the session was prepared apply. That is the caller's `options.sessionSettings`; the callee's session is prepared by the Push SDK with its own settings (session type and peer call), so layout or hidden-button settings passed by the callee have no effect. @@ -516,7 +514,7 @@ The other participant: ```kotlin -session.addSessionStatusListener(this, object : SessionStatusListener() { +CometChatCalls.getActiveSession()?.addSessionStatusListener(this, object : SessionStatusListener() { override fun onSessionLeft() = finish() override fun onConnectionClosed() = finish() }) @@ -524,7 +522,8 @@ session.addSessionStatusListener(this, object : SessionStatusListener() { ```java -session.addSessionStatusListener(this, new SessionStatusListener() { +CallSession session = CometChatCalls.getActiveSession(); +if (session != null) session.addSessionStatusListener(this, new SessionStatusListener() { @Override public void onSessionLeft() { finish(); } @Override public void onConnectionClosed() { finish(); } }); @@ -538,11 +537,11 @@ session.addSessionStatusListener(this, new SessionStatusListener() { ## Second call while in a call -Handled by the Push SDK, the same way a phone does it. A call that arrives while the user is in another call rings over it with "Answering ends your current call". **Answer** leaves the live call, prepares the new one and opens your answer activity again (with `FLAG_ACTIVITY_SINGLE_TOP`, so it arrives in `onNewIntent` if the activity is still up: open a fresh screen for the new session). **Decline** sends `busy` and keeps the live call. A call that arrives while another is already ringing, or while the user's own outgoing call is ringing, is declined as `busy` without ringing. +Handled by the Push SDK, the same way a phone does it. A call that arrives while the user is in another call rings over it with "Answering ends your current call". **Answer** leaves the live call, prepares the new one and opens your answer activity again (with `FLAG_ACTIVITY_SINGLE_TOP`, so it arrives in `onNewIntent` if the activity is still up: open a fresh screen for the new session). **Decline** sends `busy` and keeps the live call. A 1:1 call that arrives while another is already ringing, or while the user's own outgoing call is ringing, is declined as `busy` without ringing; a group call in that situation is dropped on this device without a reply. ## Group calls -`initiateCall` is for 1:1 calls. A group call is a session every member joins with `joinSession` using a session id you share with them (for example in a custom message). If a group call rings through the push, declining it or letting it ring out sends nothing: the call carries on for the other members, and the decline is local to that device. +To ring a group, call `initiateCall` with the group's GUID as `receiverUid` and `Constants.RECEIVER_TYPE_GROUP`; every member's device rings through the push. You can also start a group call without ringing: share a session id (for example in a custom message) and have each member join it with `joinSession`. When a group call rings, declining it or letting it ring out sends nothing: the call carries on for the other members, and the decline is local to that device. ## Logout diff --git a/calls/android/setup.mdx b/calls/android/setup.mdx index d60945a63..d53782a67 100644 --- a/calls/android/setup.mdx +++ b/calls/android/setup.mdx @@ -2,7 +2,7 @@ title: "Setup" sidebarTitle: "Setup" sdk_version: "5.x" -description: "Set up CometChat Calls SDK v5 for Android with installation, app credentials, initialization, host overrides, authentication, and the push dependencies needed for ringing." +description: "Set up CometChat Calls SDK v5 for Android with installation, app credentials, initialization, authentication, and the push dependencies needed for ringing." --- @@ -270,8 +270,9 @@ CometChatCalls.init(this, callAppSettings, new CometChatCalls.CallbackListener - **Initialize once.** The SDK keeps the settings from its first `init()`; a later `init()` is ignored for the rest - of the process. If you use the CometChat UI Kit with `enableCalling`, it initializes the Calls SDK for you, so do + **Initialize once.** The SDK keeps the settings from its first `init()`; a later `init()` with the same app ID is + ignored for the rest of the process. A later `init()` with a different app ID logs the user out and re-creates the + SDK with the new settings. If you use the CometChat UI Kit with `enableCalling`, it initializes the Calls SDK for you, so do not call `init()` again yourself. diff --git a/calls/android/voip-calling.mdx b/calls/android/voip-calling.mdx index a4512282c..687b199d5 100644 --- a/calls/android/voip-calling.mdx +++ b/calls/android/voip-calling.mdx @@ -44,9 +44,9 @@ dependencies { Adding the calls artifact turns ringing on. The Chat SDK is optional; a calls-only app works. -## Step 2: Initialise once +## Step 2: Initialise on every app start -Run this once per install, for example in `Application.onCreate`. The configuration is saved to disk, so a push that wakes a cold process still finds it. +Run this in `Application.onCreate`, on every app start, before the user logs in. Token registration on login and logout only runs in a process where `init` ran. The configuration is also saved to disk, so a push that wakes a killed app still rings before your code runs. @@ -79,12 +79,12 @@ CometChatPushNotifications.init( | Setter | Default | What it does | |--------|---------|--------------| -| `setProviderId(id)` | none | The dashboard FCM provider the token is registered under | +| `setProviderId(id)` | none; **required** | The dashboard FCM provider the token is registered under. Without it no token is registered, so nothing rings | | `setAnswerActivity(Class)` | none, **required** | Started when the user answers, with the call in its extras | | `setIncomingCallActivity(Class?)` | the SDK's own screen | Your full-screen ring screen; see [Your own incoming-call screen](#your-own-incoming-call-screen) | | `setIncomingCallTheme(theme?)` | none | Colours of the SDK's incoming-call screen and call notifications, light and dark; see [Theming the SDK's incoming-call screen](#theming-the-sdks-incoming-call-screen) | | `setMissedCallActivity(Class?)` | none | Opened when a missed-call notification is tapped. Without it an individual missed-call notification has no tap action | -| `setNotificationSmallIcon(res)` | none | Status-bar icon for every notification | +| `setNotificationSmallIcon(res)` | a system mail icon | Status-bar icon for chat notifications. Call notifications use the drawables `cometchat_pn_ic_notification_call` and `cometchat_pn_ic_notification_call_missed`; override those by name | | `setDefaultRingTimeoutMs(ms)` | 45 000 | Ring window when the push carries no `ringTimeout`; 1 s to 10 min | | `setShowMissedCallNotification(bool)` | `true` | Post a missed-call notification | | `setRequestCallPermissions(bool)` | `true` | Ask for microphone (and camera) on Answer | @@ -162,8 +162,10 @@ class CallScreenActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_call) - setShowWhenLocked(true) - setTurnScreenOn(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { // the manifest attributes cover 27+ + setShowWhenLocked(true) + setTurnScreenOn(true) + } val call = CallBundles.getCall(intent) ?: return finish() val settings = CometChatCalls.SessionSettingsBuilder() @@ -176,12 +178,14 @@ class CallScreenActivity : AppCompatActivity() { }) } - // A second call answered over this one arrives here (SINGLE_TOP): open a fresh screen for it. + // A second call answered over this one arrives here (the answer intent is SINGLE_TOP): open a + // fresh screen for it. setFlags replaces the copied SINGLE_TOP | CLEAR_TOP, which would route + // the launch back to this instance. override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) val next = CallBundles.getCall(intent) ?: return if (next.sessionId != CallBundles.getCall(getIntent())?.sessionId) { - startActivity(Intent(intent).setClass(this, CallScreenActivity::class.java)) + startActivity(Intent(intent).setClass(this, CallScreenActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) finish() } } @@ -196,8 +200,10 @@ public class CallScreenActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_call); - setShowWhenLocked(true); - setTurnScreenOn(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { // the manifest attributes cover 27+ + setShowWhenLocked(true); + setTurnScreenOn(true); + } IncomingCall call = CallBundles.getCall(getIntent()); if (call == null) { finish(); return; } @@ -211,14 +217,16 @@ public class CallScreenActivity extends AppCompatActivity { }); } - // A second call answered over this one arrives here (SINGLE_TOP): open a fresh screen for it. + // A second call answered over this one arrives here (the answer intent is SINGLE_TOP): open a + // fresh screen for it. setFlags replaces the copied SINGLE_TOP | CLEAR_TOP, which would route + // the launch back to this instance. @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); IncomingCall next = CallBundles.getCall(intent); IncomingCall current = CallBundles.getCall(getIntent()); if (next != null && (current == null || !next.getSessionId().equals(current.getSessionId()))) { - startActivity(new Intent(intent).setClass(this, CallScreenActivity.class)); + startActivity(new Intent(intent).setClass(this, CallScreenActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); finish(); } } @@ -229,32 +237,23 @@ public class CallScreenActivity extends AppCompatActivity { `IncomingCall` gives you `sessionId`, `callerUid`, `callerName`, `callerAvatarUrl`, `isVideo`, `receiverType` and `receiverUid`. -Declare the activity so it can show over the lock screen: +Declare the activity so it can show over the lock screen. Leave the launch mode at its default: the answer intent already carries `SINGLE_TOP`, and a `singleTop` launch mode would also stop `onNewIntent` from starting a fresh screen for a second call. ```xml AndroidManifest.xml ``` -The SDK posts no notification during the call. For a foreground service while the call runs, see [Background Handling](/calls/android/background-handling). +After Answer, the ring becomes a quiet "Connecting call…" notification until the session reports joined; the SDK posts no notification once the call has joined. For a foreground service while the call runs, see [Background Handling](/calls/android/background-handling). ## Step 5: Permissions and ring readiness -Declare in the manifest what the SDKs only request: - -```xml AndroidManifest.xml - - - -``` - -Everything else comes from the calls module's own manifest. +There is nothing to declare: `POST_NOTIFICATIONS`, `RECORD_AUDIO` and `CAMERA` merge into your manifest from the Calls SDK and the calls module, along with everything else ringing needs. You only request them at runtime. **Microphone and camera.** The SDK asks when the user answers. A denied microphone joins muted and does not fail the call. Ask earlier as well (at sign-in, for example) so the first call is not where the user meets the prompt, and give your call screen a way to the app's settings page for a permission denied for good. If your app prompts itself, turn the SDK prompt off with `setRequestCallPermissions(false)`. @@ -299,7 +298,7 @@ Call `ensureRingReady` only after any other permission request of yours has retu ## How a ring ends -The SDK handles every way a ring ends; you write no code for it. A decline sends `rejected` to the caller (`busy` if the user is in another call). A 1:1 call nobody answers stops after the ring window (45 s by default), posts a missed call and sends `unanswered`. A cancel by the caller, or an answer on another device, stops the ring here. +The SDK handles every way a ring ends; you write no code for it. A decline sends `rejected` to the caller (`busy` if the user is in another call). A 1:1 call nobody answers stops after the ring window (45 s by default), posts a missed call and sends `unanswered`. For a group call, a decline or a busy only stops the ring on this device: nothing is sent and the call continues for the others; a group call nobody answers here still posts a missed call. A cancel by the caller, or an answer on another device, stops the ring here. A call that arrives during another call rings over it with "Answering ends your current call". Answering leaves the live call and joins the new one; declining sends `busy` and the live call continues. @@ -313,7 +312,7 @@ The high-priority push starts the app process and the SDK rings from its saved c ## Tokens and logout -You do not register the push token: the SDK registers it on login and unregisters it on logout. Log out of the Calls SDK when the user signs out (`CometChatCalls.logout`, or `CometChatUIKit.logout` with the UI Kit). That stops a ring still playing for the old user and unregisters the token; `CometChat.logout` alone does neither. +You do not register the push token: the SDK registers it on login and unregisters it on logout. Log out of the Calls SDK when the user signs out (`CometChatCalls.logout`, or `CometChatUIKit.logout` with the UI Kit). That stops a ring still playing for the old user and unregisters the token. `CometChat.logout` alone unregisters the token but does not stop a ring or sign out of the Calls SDK. ## Your own incoming-call screen @@ -327,7 +326,7 @@ class MyRingActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setShowWhenLocked(true); setTurnScreenOn(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { setShowWhenLocked(true); setTurnScreenOn(true) } val call = CallBundles.getCall(intent) ?: return finish() this.call = call setContentView(R.layout.activity_ring) @@ -361,7 +360,7 @@ public class MyRingActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setShowWhenLocked(true); setTurnScreenOn(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { setShowWhenLocked(true); setTurnScreenOn(true); } call = CallBundles.getCall(getIntent()); if (call == null) { finish(); return; } setContentView(R.layout.activity_ring); @@ -392,6 +391,18 @@ public class MyRingActivity extends AppCompatActivity { Without `onIncomingCallScreenOpened`, a cancelled call leaves your screen on the lock screen showing an Answer button for a call that no longer exists. +Declare the screen so it can show over the lock screen and turn the display on, as the SDK's own screen is declared: + +```xml AndroidManifest.xml + +``` + ## Theming the SDK's incoming-call screen The SDK's incoming-call screen and its call notifications take each colour from, in order: diff --git a/notifications/android-push-notifications.mdx b/notifications/android-push-notifications.mdx index 5cb2bf709..9bf57f410 100644 --- a/notifications/android-push-notifications.mdx +++ b/notifications/android-push-notifications.mdx @@ -45,7 +45,7 @@ From the same screen, click **Add Provider** to upload the Firebase service acco Upload FCM service account JSON -3. **App constants**: note your CometChat App ID, Auth Key and Region, and the FCM **Provider ID** you chose. The Provider ID goes into `PNConfiguration.setProviderId`; the SDK registers the device token under it. +3. **App constants**: note your CometChat App ID, Auth Key and Region, and the FCM **Provider ID** you chose. The Provider ID goes into `PNConfiguration.setProviderId`, and it is required: the SDK registers the device token under it, and without it no token is registered. ## 2. Add dependencies (Gradle) @@ -99,19 +99,17 @@ dependencies { -- The UI Kit already depends on both push artifacts; declaring them yourself only pins the version. +- The UI Kit does not include the push artifacts; add both, also in a UI Kit app. - Apply the `google-services` plugin and place `google-services.json` in the same module. - `minSdk 26`: the calls module inherits the Calls SDK's floor. The core artifact alone works from 24. -- The Chat SDK is optional for the push SDK: a calls-only app ships without it. Chat notifications and inline reply need it. +- The Chat SDK is optional for the push SDK: a calls-only app ships without it. Chat pushes are still shown without it; delivery receipts, inline reply and tap deep links need it. ## 3. Manifest permissions and services ```xml lines - + - - - diff --git a/ui-kit/android/call-buttons.mdx b/ui-kit/android/call-buttons.mdx index 35b42e344..9a26bcdcf 100644 --- a/ui-kit/android/call-buttons.mdx +++ b/ui-kit/android/call-buttons.mdx @@ -225,8 +225,7 @@ CometChatCallButtons( | Event | Fires when | Payload | | --- | --- | --- | | `CometChatCallEvent.OutgoingCall` | An outgoing 1:1 call was created | `Call` | -| `CometChatCallEvent.CallRejected` | The outgoing screen closed without an answer (End tapped, declined, busy, unanswered, cancelled) | `Call` | -| `CometChatCallEvent.CallEnded` | A 1:1 call ended on this device | `Call` | +| `CometChatCallEvent.CallRejected` | The outgoing screen's End was tapped, or its ring timer (45 s) expired. Declined, busy and unanswered emit no global event | `Call` | ### SDK Events (Real-Time, Automatic) diff --git a/ui-kit/android/call-features.mdx b/ui-kit/android/call-features.mdx index 683d1f5e8..fa15a4750 100644 --- a/ui-kit/android/call-features.mdx +++ b/ui-kit/android/call-features.mdx @@ -7,8 +7,8 @@ description: "Add one-on-one and group audio/video calling to your Android app u | Field | Value | | --- | --- | -| Kotlin (XML Views) | `com.cometchat:chatuikit-kotlin-android` (brings `calls-sdk-android`, `push-notifications-android`, `push-notifications-calls-android`) | -| Jetpack Compose | `com.cometchat:chatuikit-compose-android` (same transitive SDKs) | +| Kotlin (XML Views) | `com.cometchat:chatuikit-kotlin-android` (brings `calls-sdk-android`); the app adds `push-notifications-android` and `push-notifications-calls-android` | +| Jetpack Compose | `com.cometchat:chatuikit-compose-android` (same: brings `calls-sdk-android`; the app adds both push artifacts) | | Required setup | `CometChatUIKit.init()` with `setEnableCalling(true)`, `CometChatUIKit.login()`, `CometChatPushNotifications.init()` with `setAnswerActivity(CallAnswerActivity::class.java)` (a small activity that opens `CometChatOngoingCallActivity`), a `FirebaseMessagingService` that calls `handlePushNotification` | | Incoming calls | Rung by the Push Notifications SDK (notification, full-screen screen, ringtone), in every app state. No call listener, no incoming-call component | | Call features | Call Buttons, Outgoing Call, Ongoing Call, Call Logs, Call Log Details | diff --git a/ui-kit/android/calling-integration.mdx b/ui-kit/android/calling-integration.mdx index c84e96ed9..6b20bda5f 100644 --- a/ui-kit/android/calling-integration.mdx +++ b/ui-kit/android/calling-integration.mdx @@ -33,13 +33,15 @@ What you write: the UI Kit settings with calling enabled, the Push SDK configura ## Step 1: Dependencies -The UI Kit core module depends on the Calls SDK and on both Push Notifications SDK artifacts, so your app gets them transitively. Add the UI Kit module and Firebase Messaging: +The UI Kit brings the Calls SDK. Add the UI Kit module, the two Push Notifications SDK artifacts and Firebase Messaging. List the UI Kit before the Push SDK artifacts, so the UI Kit's incoming-call colours take effect (see [Styling the incoming-call screen](#styling-the-incoming-call-screen)): ```kotlin build.gradle.kts dependencies { implementation("com.cometchat:chatuikit-kotlin-android:6.1.0") + implementation("com.cometchat:push-notifications-android:2.0.0") + implementation("com.cometchat:push-notifications-calls-android:2.0.0") implementation(platform("com.google.firebase:firebase-bom:33.7.0")) implementation("com.google.firebase:firebase-messaging") } @@ -50,6 +52,8 @@ dependencies { ```kotlin build.gradle.kts dependencies { implementation("com.cometchat:chatuikit-compose-android:6.1.0") + implementation("com.cometchat:push-notifications-android:2.0.0") + implementation("com.cometchat:push-notifications-calls-android:2.0.0") implementation(platform("com.google.firebase:firebase-bom:33.7.0")) implementation("com.google.firebase:firebase-messaging") } @@ -57,7 +61,7 @@ dependencies { -Apply the `google-services` plugin and put `google-services.json` in the app module. `minSdk` must be 26 or higher: the Calls SDK's React Native runtime declares it. +Apply the `google-services` plugin and put `google-services.json` in the app module. `minSdk` must be 28 or higher, the UI Kit's minimum. To pin the SDK versions yourself, declare them explicitly; the UI Kit requires `calls-sdk-android` 5.1 or newer and `push-notifications-android` / `push-notifications-calls-android` 2.0 or newer. @@ -230,17 +234,9 @@ To colour the incoming-call screen with your UI Kit theme, see [Styling the inco ## Step 4: Permissions -```xml AndroidManifest.xml - - - -``` - -The remaining permissions come from the Push SDK's manifest. - -At runtime: +There is nothing to declare. `RECORD_AUDIO`, `CAMERA` and `POST_NOTIFICATIONS` merge into your manifest from the UI Kit, the Calls SDK and the Push SDK's calls module, along with the permissions ringing needs. You only request them at runtime: -- **Notifications**: ask for `POST_NOTIFICATIONS` (Android 13+) after sign-in, then call `CometChatPushNotifications.ensureRingReady(activity)` and show the user anything other than `READY`. Notifications off, a muted call channel, a revoked full-screen intent and Do Not Disturb all silence calls with no error. +- **Notifications**: after sign-in, call `CometChatPushNotifications.ensureRingReady(activity)` and show the user anything other than `READY`. On Android 13+ it asks for `POST_NOTIFICATIONS` itself when it is missing, and reports `NOTIFICATIONS_OFF` while that prompt is still on screen. Notifications off, a muted call channel, a revoked full-screen intent and Do Not Disturb all silence calls with no error. - **Microphone and camera**: the Push SDK asks when the user answers, and the UI Kit's call screen asks once more when it opens; a denied microphone joins muted. Ask at sign-in as well if you do not want the first call to be where the user meets the prompt. ```kotlin @@ -252,7 +248,7 @@ CometChatPushNotifications.ensureRingReady(this) { result -> } ``` -Run `ensureRingReady` only after your own permission request has returned; Android drops a request made while another dialog is open. +If your app asks for other permissions at sign-in, call `ensureRingReady` after those requests have returned; Android drops a permission request made while another dialog is open. ## Step 5: Log out through the UI Kit @@ -314,7 +310,7 @@ lifecycleScope.launch { } ``` -`CometChatCallActivity` is `com.cometchat.uikit.kotlin.calls.CometChatCallActivity` for XML Views and `com.cometchat.uikit.compose.calls.CometChatCallActivity` for Compose. A call is refused with `ACTIVE_CALL` while another session is active. +`CometChatCallActivity` is `com.cometchat.uikit.kotlin.calls.CometChatCallActivity` for XML Views and `com.cometchat.uikit.compose.calls.CometChatCallActivity` for Compose. `initiateUserCall` fails with `CALL_IN_PROGRESS` while another start is in flight; it does not check for a call already in progress, so do that yourself, or use [Call Buttons](/ui-kit/android/call-buttons), which refuse with `ACTIVE_CALL` while another session is active. ## Styling the incoming-call screen @@ -387,7 +383,7 @@ Each field comes from one UI Kit colour: Without this line, the screen still uses the UI Kit's default colours. The UI Kit overrides the `cometchat_pn_call_*` colour resources with its light and dark palette at build time. Those overrides win over the Push SDK's defaults only when your app lists the UI Kit dependency before the Push SDK artifacts. Your app's own overrides always win. -To change a single colour, override its resource, or chain any `CometChatPNIncomingCallTheme.Colors.Builder` setter after mapping. The full list of colours, strings, drawables and the layout is under [Theming the SDK's incoming-call screen](/calls/android/voip-calling#theming-the-sdks-incoming-call-screen). To draw the screen yourself, register the UI Kit's `CometChatCallActivity` or your own activity with `setIncomingCallActivity`; see the deprecated [Incoming Call](/ui-kit/android/incoming-call) component. +To change a single colour, override its resource, or chain any `CometChatPNIncomingCallTheme.Colors.Builder` setter after mapping. The full list of colours, strings, drawables and the layout is under [Theming the SDK's incoming-call screen](/calls/android/voip-calling#theming-the-sdks-incoming-call-screen). To draw the screen yourself, register your own activity with `setIncomingCallActivity`, for example one that hosts the deprecated [Incoming Call](/ui-kit/android/incoming-call) component. The UI Kit's `CometChatCallActivity` does not work there: it does not read the call the Push SDK passes in its intent, so it would open empty. ## Upgrading from an app that used `CometChat.addCallListener` diff --git a/ui-kit/android/color-resources.mdx b/ui-kit/android/color-resources.mdx index 86f6987e1..dcd499085 100644 --- a/ui-kit/android/color-resources.mdx +++ b/ui-kit/android/color-resources.mdx @@ -225,7 +225,7 @@ This means overriding a neutral color automatically updates all tokens that refe The incoming-call screen is drawn by the Push Notifications SDK, which cannot read the UI Kit theme when a call arrives with the app killed. It takes its colours from two places: - **A theme passed in code.** `CometChatTheme.getIncomingCallTheme(context)` (XML Views) or `incomingCallTheme(light, dark)` (Compose) reads your UI Kit theme, light and dark, and your app passes it to the Push SDK with `setIncomingCallTheme`. A palette changed in code, including at runtime, reaches the screen this way. See [Styling the incoming-call screen](/ui-kit/android/calling-integration#styling-the-incoming-call-screen). -- **Colour resources.** These apply to any colour the theme doesn't set. The UI Kit overrides them with its palette, and your app's `res/values/` and `res/values-night/` overrides win over both. +- **Colour resources.** These apply to any colour the theme doesn't set. The UI Kit overrides six of them with its palette (background, text, secondary text, avatar, accept and decline); the other five keep the Push SDK's defaults, which follow those six, so the values below hold for all of them. The UI Kit's overrides take effect only when your app lists the UI Kit dependency before the Push SDK artifacts. Your app's own `res/values/` and `res/values-night/` overrides always win. | Resource | UI Kit colour | UI Kit light | UI Kit dark | Used for | | --- | --- | --- | --- | --- | diff --git a/ui-kit/android/getting-started-jetpack.mdx b/ui-kit/android/getting-started-jetpack.mdx index 206104d3b..4f35ce9d2 100644 --- a/ui-kit/android/getting-started-jetpack.mdx +++ b/ui-kit/android/getting-started-jetpack.mdx @@ -14,7 +14,7 @@ description: "Step-by-step guide to integrate the CometChat Jetpack Compose UI K | Login | `CometChatUIKit.login("UID", callback)` — must resolve before rendering components | | Order | `init()` → `login()` → render. Breaking this order = blank screen | | Auth Key | Dev/testing only. Use Auth Token in production | -| Calling | Optional. `setEnableCalling(true)` in `UIKitSettings` + push ringing setup; the Calls and Push SDKs ship with the UI Kit | +| Calling | Optional. `setEnableCalling(true)` in `UIKitSettings` + push ringing setup. The Calls SDK ships with the UI Kit; push ringing adds the Push Notifications SDK and Firebase Messaging | | Min SDK | Android 9.0 (API 28) | @@ -48,7 +48,7 @@ Auth Key is for development only. In production, generate Auth Tokens server-sid -**Calling.** Set `enableCalling` to `true` to turn on voice and video calling; the UI Kit then initialises the Calls SDK and logs the user into it. The Calls SDK and the Push Notifications SDK ship with the UI Kit, so there is no extra dependency, but incoming calls only ring once push ringing is configured. See [Calling Integration](/ui-kit/android/calling-integration). Leave the flag `false` in an app that does not use calling. +**Calling.** Set `enableCalling` to `true` to turn on voice and video calling; the UI Kit then initialises the Calls SDK and logs the user into it. The Calls SDK ships with the UI Kit. Incoming calls ring through the Push Notifications SDK, which your app adds along with Firebase Messaging when it sets up push ringing. See [Calling Integration](/ui-kit/android/calling-integration). Leave the flag `false` in an app that does not use calling. ## Step 1 — Create an Android Project @@ -105,8 +105,8 @@ android { dependencies { // CometChat Jetpack Compose UI Kit implementation("com.cometchat:chatuikit-compose-android:6.1.0") - // Voice/video calling needs nothing more here: the Calls SDK and the Push Notifications SDK - // come with the UI Kit. See Calling Integration for push ringing (Firebase Messaging). + // Voice/video calling: the Calls SDK comes with the UI Kit. Incoming calls ring through the + // Push Notifications SDK and Firebase Messaging, added in Calling Integration. } ``` diff --git a/ui-kit/android/getting-started-kotlin.mdx b/ui-kit/android/getting-started-kotlin.mdx index 422b7641c..80cbb5e1d 100644 --- a/ui-kit/android/getting-started-kotlin.mdx +++ b/ui-kit/android/getting-started-kotlin.mdx @@ -14,7 +14,7 @@ description: "Step-by-step guide to integrate the CometChat Kotlin UI Kit with X | Login | `CometChatUIKit.login("UID", callback)` — must resolve before rendering components | | Order | `init()` → `login()` → render. Breaking this order = blank screen | | Auth Key | Dev/testing only. Use Auth Token in production | -| Calling | Optional. `setEnableCalling(true)` in `UIKitSettings` + push ringing setup; the Calls and Push SDKs ship with the UI Kit | +| Calling | Optional. `setEnableCalling(true)` in `UIKitSettings` + push ringing setup. The Calls SDK ships with the UI Kit; push ringing adds the Push Notifications SDK and Firebase Messaging | | Min SDK | Android 9.0 (API 28) | @@ -48,7 +48,7 @@ Auth Key is for development only. In production, generate Auth Tokens server-sid -**Calling.** Set `enableCalling` to `true` to turn on voice and video calling; the UI Kit then initialises the Calls SDK and logs the user into it. The Calls SDK and the Push Notifications SDK ship with the UI Kit, so there is no extra dependency, but incoming calls only ring once push ringing is configured. See [Calling Integration](/ui-kit/android/calling-integration). Leave the flag `false` in an app that does not use calling. +**Calling.** Set `enableCalling` to `true` to turn on voice and video calling; the UI Kit then initialises the Calls SDK and logs the user into it. The Calls SDK ships with the UI Kit. Incoming calls ring through the Push Notifications SDK, which your app adds along with Firebase Messaging when it sets up push ringing. See [Calling Integration](/ui-kit/android/calling-integration). Leave the flag `false` in an app that does not use calling. ## Step 1 — Create an Android Project @@ -100,8 +100,8 @@ android { dependencies { // CometChat Kotlin UI Kit implementation("com.cometchat:chatuikit-kotlin-android:6.1.0") - // Voice/video calling needs nothing more here: the Calls SDK and the Push Notifications SDK - // come with the UI Kit. See Calling Integration for push ringing (Firebase Messaging). + // Voice/video calling: the Calls SDK comes with the UI Kit. Incoming calls ring through the + // Push Notifications SDK and Firebase Messaging, added in Calling Integration. } ``` diff --git a/ui-kit/android/getting-started.mdx b/ui-kit/android/getting-started.mdx index f03716903..feba2f9b8 100644 --- a/ui-kit/android/getting-started.mdx +++ b/ui-kit/android/getting-started.mdx @@ -15,7 +15,7 @@ description: "Install, configure, and launch the CometChat Android UI Kit in you | Order | `init()` → `login()` → render. Breaking this order = blank screen | | Auth Key | Dev/testing only. Use Auth Token in production | | Theme | Set `CometChatTheme.DayNight` as parent theme in `themes.xml` | -| Calling | Optional. `setEnableCalling(true)` in `UIKitSettings` + push ringing setup; the Calls and Push SDKs ship with the UI Kit | +| Calling | Optional. `setEnableCalling(true)` in `UIKitSettings` + push ringing setup. The Calls SDK ships with the UI Kit; push ringing adds the Push Notifications SDK and Firebase Messaging | | Min SDK | Android 9.0 (API 28) | @@ -114,8 +114,8 @@ Now, in your app-level `build.gradle.kts` file: ```kotlin build.gradle.kts dependencies { implementation(libs.cometchat.ui.kit) - // Voice/video calling needs nothing more here: the Calls SDK and the Push Notifications SDK - // come with the UI Kit. See Calling Integration for push ringing (Firebase Messaging). + // Voice/video calling: the Calls SDK comes with the UI Kit. Incoming calls ring through the + // Push Notifications SDK and Firebase Messaging, added in Calling Integration. } ``` @@ -129,7 +129,7 @@ Open the app-level `build.gradle.kts` file and add the dependency for your chose ```kotlin build.gradle.kts dependencies { implementation("com.cometchat:chatuikit-kotlin-android:6.1.0") - // Calling: nothing more to add; see Calling Integration for push ringing + // Calling: the Calls SDK comes with the UI Kit; push ringing is added in Calling Integration } ``` @@ -138,7 +138,7 @@ dependencies { ```kotlin build.gradle.kts dependencies { implementation("com.cometchat:chatuikit-compose-android:6.1.0") - // Calling: nothing more to add; see Calling Integration for push ringing + // Calling: the Calls SDK comes with the UI Kit; push ringing is added in Calling Integration } ``` diff --git a/ui-kit/android/incoming-call.mdx b/ui-kit/android/incoming-call.mdx index c638a4104..0821e08c2 100644 --- a/ui-kit/android/incoming-call.mdx +++ b/ui-kit/android/incoming-call.mdx @@ -83,7 +83,19 @@ class RingActivity : AppCompatActivity() { } ``` -In Compose, pass the same calls as `onAcceptClick` and `onRejectClick` to `CometChatIncomingCall(call = call, ...)` inside `setContent`. +In Compose, pass `CometChatCallNotifications.answer` through `onAcceptRequested` and `decline` through `onRejectClick`, inside `setContent`. Do not use `onAcceptClick` for this: in Compose it runs after the component has already accepted the call and opened `CometChatOngoingCallActivity`. + +```kotlin +CometChatIncomingCall( + call = call, + // Replaces the default accept: proceed() is never called, so the component does not join itself. + onAcceptRequested = { _ -> CometChatCallNotifications.answer(this@RingActivity, incoming.sessionId) }, + onRejectClick = { + CometChatCallNotifications.decline(this@RingActivity, incoming.sessionId) + finish() + } +) +``` Prerequisites: `CometChatUIKit.init()` with `setEnableCalling(true)`, the Push SDK initialised with an answer activity, and a user logged in. @@ -95,7 +107,7 @@ Prerequisites: `CometChatUIKit.init()` with `setEnableCalling(true)`, the Push S #### `onAcceptClick` -Fires when the user taps Accept. By default the component opens `CometChatOngoingCallActivity` itself. On a Push SDK ring screen, set it to call `CometChatCallNotifications.answer(context, sessionId)` instead, so the Push SDK stops the ring and opens your answer activity. Ask for the microphone (and camera) before answering: the Push SDK only prompts for its notification's Answer action. +Fires when the user taps Accept. By default the component opens `CometChatOngoingCallActivity` itself. In XML Views, `setOnAcceptClickListener` replaces that default: on a Push SDK ring screen, set it to call `CometChatCallNotifications.answer(context, sessionId)`, so the Push SDK stops the ring and opens your answer activity. In Compose, `onAcceptClick` does not replace the default; it runs after the component has accepted and opened the call screen. Use `onAcceptRequested` instead, and don't call the `proceed` it receives. Ask for the microphone (and camera) before answering: the Push SDK only prompts for its notification's Answer action. @@ -180,8 +192,8 @@ CometChatIncomingCall( | Event | Fires when | Payload | | --- | --- | --- | -| `CometChatCallEvent.CallAccepted` | Accept tapped on this screen | `Call` | -| `CometChatCallEvent.CallRejected` | Decline tapped on this screen | `Call` | +| `CometChatCallEvent.CallAccepted` | Accept tapped on this screen, with the default accept (not emitted when you replace it) | `Call` | +| `CometChatCallEvent.CallRejected` | Decline tapped on this screen, with the default decline (not emitted when you replace it) | `Call` | ### SDK Events (Real-Time, Automatic) diff --git a/ui-kit/android/ongoing-call.mdx b/ui-kit/android/ongoing-call.mdx index d5a795eb2..f23834e71 100644 --- a/ui-kit/android/ongoing-call.mdx +++ b/ui-kit/android/ongoing-call.mdx @@ -161,7 +161,7 @@ Fires when this device has left the session: the user tapped End, the other side -The hosting `CometChatOngoingCallActivity` finishes itself. When you embed the component, observe the ViewModel's events: +The hosting `CometChatOngoingCallActivity` finishes itself, and an embedded component finishes its host activity on `CallEnded` and `SessionTimeout` too. Observe the ViewModel's events only for your own clean-up: ```kotlin lines lifecycleScope.launch { @@ -187,7 +187,7 @@ CometChatOngoingCall( #### `onError` -Fires when the session could not be joined (bad session id, no network, the Calls SDK not logged in) or the microphone permission is missing. +Fires when the session could not be joined (bad session id, no network, the Calls SDK not logged in). A denied microphone does not fire it: the call joins muted. @@ -222,13 +222,13 @@ CometChatOngoingCall( | `SessionTimeout` | The session timed out | | `UserJoined(userId, isCurrentUser)` | A participant joined | | `UserLeft(userId)` | A participant left | -| `Error(exception)` | Join failed or a permission is missing | +| `Error(exception)` | Joining the session failed | ### Global UI Events (`CometChatEvents.callEvents`) | Event | Fires when | Payload | | --- | --- | --- | -| `CometChatCallEvent.CallEnded` | A 1:1 call ends on this device | `Call` (the answered call, when the screen was opened by the Push SDK) | +| `CometChatCallEvent.CallEnded` | This device hangs up a 1:1 call (End, or the call screen's own leave button) that the screen was opened with, as your answer activity does with `launchOngoingCallActivity(context, call)`. Not emitted when the other side hangs up, when the connection closes, or for a screen opened with a session id | `Call` | ### SDK Events (Real-Time, Automatic) @@ -259,9 +259,9 @@ CometChatOngoingCall( ## Hang-up and clean-up -Hanging up leaves the Calls SDK session with `CallSession.leave()`; nothing is sent to the Chat SDK. For a 1:1 call (`DEFAULT`) the UI Kit also clears its active-call state and emits `CometChatCallEvent.CallEnded` on `CometChatEvents.callEvents`. For a `MEETING` it only leaves and clears the meeting flag; the conference continues for the others. +Hanging up leaves the Calls SDK session with `CallSession.leave()`; nothing is sent to the Chat SDK. For a 1:1 call (`DEFAULT`) the UI Kit also clears its active-call state and, when the screen was opened with a `Call`, emits `CometChatCallEvent.CallEnded` on `CometChatEvents.callEvents`. A hang-up by the other side clears the same state but emits no global event; observe the component's `CallEnded` instead. For a `MEETING` it only leaves and clears the meeting flag; the conference continues for the others. -`CometChatOngoingCallActivity` leaves the session when it is destroyed, but only its **own** session: after answering a second call over a live one, the Push SDK opens a fresh activity for the new session (the answer intent arrives in `onNewIntent` with `SINGLE_TOP`), and the old screen's clean-up does not touch it. +`CometChatOngoingCallActivity` leaves the session when it is destroyed, but only its **own** session: after answering a second call over a live one, the Push SDK opens your answer activity again, which hands the new call to `CometChatOngoingCallActivity`. That intent arrives in `onNewIntent` (`SINGLE_TOP`); the activity starts a fresh instance for the new session and finishes itself, and the old screen's clean-up does not touch the new session. --- @@ -273,7 +273,7 @@ Hanging up leaves the Calls SDK session with `CallSession.leave()`; nothing is s ## Second call during a call -Handled by the Push SDK: the new call rings over the live one with "Answering ends your current call". Answer leaves the live session, prepares the new one and opens `CometChatOngoingCallActivity` again for it; Decline sends `busy` and keeps the current call. +Handled by the Push SDK: the new call rings over the live one with "Answering ends your current call". Answer leaves the live session, prepares the new one and opens your answer activity, which opens `CometChatOngoingCallActivity` for it; Decline sends `busy` and keeps the current call. --- diff --git a/ui-kit/android/outgoing-call.mdx b/ui-kit/android/outgoing-call.mdx index 5accc6673..cd9ef3cef 100644 --- a/ui-kit/android/outgoing-call.mdx +++ b/ui-kit/android/outgoing-call.mdx @@ -144,7 +144,7 @@ Fires on internal errors (network failure, auth issue, SDK exception). ```kotlin lines -outgoingCall.setOnError { context, exception -> +outgoingCall.setOnError { exception -> Log.e("OutgoingCall", "Error: ${exception.message}") } ``` @@ -155,7 +155,7 @@ outgoingCall.setOnError { context, exception -> ```kotlin lines CometChatOutgoingCall( call = call, - onError = { context, exception -> + onError = { exception -> Log.e("OutgoingCall", "Error: ${exception.message}") } ) @@ -179,7 +179,7 @@ CometChatOutgoingCall( | Event | Fires when | Payload | | --- | --- | --- | -| `CometChatCallEvent.CallRejected` | The screen closed without an answer: End tapped, or the call was declined, busy, unanswered or cancelled elsewhere | `Call` | +| `CometChatCallEvent.CallRejected` | End tapped, or this screen's ring timer (45 s) expired. A decline, busy, unanswered or a close from the other side emits no global event; observe the ViewModel's `rejectedCall` / `endReason` for those | `Call` | ### SDK Events (Real-Time, Automatic) diff --git a/ui-kit/android/troubleshooting.mdx b/ui-kit/android/troubleshooting.mdx index 1b341fcd1..1e2f01d3e 100644 --- a/ui-kit/android/troubleshooting.mdx +++ b/ui-kit/android/troubleshooting.mdx @@ -91,7 +91,7 @@ description: "Common failure modes and fixes for the CometChat Android V6 UI Kit | Call connects but no audio/video | Missing runtime permissions | Declare `CAMERA` and `RECORD_AUDIO` in the manifest; the Push SDK asks on Answer and the call screen asks when it opens. A denied microphone joins muted | | Sound still plays after disabling | `disableSoundForCalls(true)` called too late, or it is the incoming ringtone | `setDisableSoundForCalls` only controls the outgoing ringback; the incoming ringtone is the Push SDK's notification channel | | Custom call sound not playing | Audio file missing or wrong path | Verify the sound file exists in `res/raw` and pass the correct `@RawRes int` resource ID to `setCustomSoundForCalls` (outgoing ringback only) | -| Call events not received | Listening on the wrong SDK | Listen with `CometChatCalls.addIncomingCallListener` (a call starting or ending), or collect `CometChatEvents.callEvents` (`CometChatCallEvent.OutgoingCall`, `CallRejected`, `CallEnded`). The Chat SDK `CallListener` is superseded and fires nothing in this flow | +| Call events not received | Listening on the wrong SDK | Listen with `CometChatCalls.addIncomingCallListener` (a call starting or ending), or collect `CometChatEvents.callEvents` (`CometChatCallEvent.OutgoingCall`, `CallRejected`, `CallEnded`; `CallRejected` and `CallEnded` only fire when this device ends the call, see Outgoing Call and Ongoing Call). The Chat SDK `CallListener` is superseded and fires nothing in this flow | | Call events still firing after screen closed | Listener or coroutine scope not removed | `removeIncomingCallListener(id)` and cancel the collecting scope in `onDestroy()` or `onDestroyView()` | --- diff --git a/ui-kit/android/upgrading-from-v5.mdx b/ui-kit/android/upgrading-from-v5.mdx index 9e495cc53..758c5af7a 100644 --- a/ui-kit/android/upgrading-from-v5.mdx +++ b/ui-kit/android/upgrading-from-v5.mdx @@ -382,7 +382,7 @@ V6 rings incoming calls through the CometChat Push Notifications SDK instead of | Own ringtone, busy check, ConnectionService stack | Delete; the Push SDK does them | | `CometChatCallsConstants.*` | `UIKitConstants.CallStatusConstants` / `UIKitConstants.CallType` | -Add `CAMERA` to the manifest next to `RECORD_AUDIO` and `POST_NOTIFICATIONS`. Full walkthrough: [Calling Integration](/ui-kit/android/calling-integration) and [Migrating from Legacy Ringing](/calls/android/migrating-from-legacy-ringing). +No manifest permissions to add: `CAMERA`, `RECORD_AUDIO` and `POST_NOTIFICATIONS` merge in from the UI Kit, the Calls SDK and the Push SDK; request them at runtime. Add the two Push Notifications SDK artifacts and Firebase Messaging to your dependencies. Full walkthrough: [Calling Integration](/ui-kit/android/calling-integration) and [Migrating from Legacy Ringing](/calls/android/migrating-from-legacy-ringing).