Skip to main content
Ringing lets one user call another and lets the callee answer or decline, whether the callee’s app is in the foreground, in the background or not running at all. Two SDKs do the work:
Coming from the earlier ringing flow? The Chat SDK’s calling methods (CometChat.initiateCall, acceptCall, rejectCall, endCall, CallListener) are superseded by the Calls SDK and no longer used; a coming Chat SDK release marks them deprecated. This page describes the replacement. See Migrating from legacy ringing for a method-by-method mapping.

How ringing works

The caller creates the call with the Calls SDK. The CometChat backend sends a push to the callee. The Push SDK rings the callee’s device, and when the callee answers, your call screen joins the session. The caller’s app learns that the callee is ringing and has answered from the Calls SDK, without joining the session first. What each side sees:

Prerequisites

  • Calls SDK set up and the user logged in.
  • Push notifications configured with the Push Notifications SDK 2.0 calls module. The whole push side of ringing is on the Push ringing page; this page assumes it is done.
  • 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, compileSdk 36 and Android Gradle Plugin 8.9.1 or later: the Push Notifications SDK 2.0 depends on androidx.core 1.18, which requires them.
build.gradle.kts
Send call pushes at high FCM priority. At normal priority the call still rings, but Android refuses the Push SDK’s foreground service, so the process can be killed between the ring and the answer.

Start a call

CometChatCalls.initiateCall creates the call on the backend, which rings the callee, and prepares the caller’s media session. The caller does not join yet.
InitiatedCall gives you sessionId, messageId (the id of the call message in the conversation), sender, senderName, senderAvatar, receiver, receiverType, type, status, initiatedAt and conversationId.
Always set the session type in sessionSettings. The Calls SDK freezes the session settings when it prepares the session and its default is video, so an audio call started without setSessionType(SessionType.VOICE) opens the caller’s camera. The settings you pass here are the ones the caller later joins with, so pass the same builder to joinSession.
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.

Show “Ringing…” and detect the answer

The caller stays on the session that initiateCall prepared and listens to it. Do not call joinSession yet: joining only when the callee has joined keeps the caller’s media off until there is someone to talk to, and it is what makes the two events below arrive.
Both listeners are lifecycle-aware: pass your activity or fragment as the LifecycleOwner and the SDK removes them when it is destroyed.

Listen for calls starting and ending

CometChatCalls.addIncomingCallListener is the one place a call announces itself and its end, on both sides. The Push SDK feeds it from the push, so it fires even when nothing of yours was running when the push arrived.
Listener ids are keys. Registering the same id twice replaces the first listener instead of adding a second one, so give each screen a stable id and remove it when the screen is destroyed.

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.Builder.setAnswerActivity. That activity reads the call from its intent and joins. There is no separate “accept” call to make.
IncomingCall carries sessionId, callerUid, callerName, callerAvatarUrl, callType, receiverType, conversationId and isVideo.

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. 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.
See Join Session for the container, Session Settings for layouts and controls, and Events for everything the joined session reports.

Decline a call

Declining is handled by the Push SDK: the Decline button on the notification and on its incoming-call screen sends endCall(sessionId, "rejected"), and sends "busy" instead when the user is already in another call. The caller receives onIncomingCallEnded with that reason. You only write decline code when you draw your own incoming-call screen:
Without the Push SDK calls module, decline through the Calls SDK directly: CometChatCalls.endCall(sessionId, Constants.CALL_STATUS_REJECTED, listener).

Cancel a call

The caller ends a call that is still ringing with endCall and the cancelled status. The callee’s ring stops and a missed call is posted there.
Disable the End button after the first tap, and ignore an onParticipantJoined that arrives after you cancelled: the cancel wins.

Ring timeout

The callee’s Push SDK ends an unanswered 1:1 call as unanswered when its ring window closes (45 seconds unless the push carries a ringTimeout). To close the caller’s screen at the same moment even if that signal is late, run your own timer from initiateCall and end the call with the same status:

End a call

Hang up by leaving the session. In a 1:1 call, set setPeerCall(true) before the session is prepared, or a caller who cancels while the callee is joining leaves the callee alone in the room. The room tells the other side, which receives onParticipantLeft and then onSessionLeft or onConnectionClosed. No status needs to be sent and the call is logged by the backend.
The other participant:
CallSession.leaveSession() is deprecated in 5.1; use leave(). Leave only the session your screen owns: after answering a second call over a live one, the first screen’s clean-up must not leave the new session.

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

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

Log out of the Calls SDK when the user signs out (CometChatCalls.logout). On that logout the Push SDK stops a call still ringing for the old user and unregisters the push token. Logging out of the Chat SDK alone leaves the Calls SDK signed in, so a ring keeps playing for the old user and later declines go out with the old token. End or cancel any call of your own before logging out; the Calls SDK cannot send after the session is gone.

Call status values