Skip to content
Last updated

Handle incoming calls

In the previous section you added the ability to initiate a call. This section describes how to receive notifications and handle incoming calls.

If you have not yet initiated a call, first follow the steps in Make an audio call.

Testing Tip

For best audio testing results use two physical Android devices. You can initiate calls with an emulator, but audio reliability is higher on real devices.

Set up FCM push notifications

To receive notifications about incoming calls, your application must integrate either FCM or HMS push delivery. A comparison and setup instructions are available in the Push notifications guide. This guide uses FCM. Ensure you have generated and downloaded your google-services.json file.

  1. Copy your google-services.json file into the <your-android-application-name>/app/ directory.

  2. Create a FcmListenerService class that extends FirebaseMessagingService. Declare the service inside AndroidManifest.xml.

    app/src/main/AndroidManifest.xml

         <service
             android:name=".fcm.FcmListenerService"
             android:exported="false">
             <intent-filter>
                 <action android:name="com.google.firebase.MESSAGING_EVENT" />
             </intent-filter>
         </service>
  3. Inside the service's onMessageReceived callback, confirm the payload indicates an incoming call. If it does, pass it to the bound SinchService and let the Sinch client handle notifying observers.

    app/src/main/java/com/sinch/rtc/sample/push/fcm/FcmListenerService.kt

     override fun onMessageReceived(remoteMessage: RemoteMessage) {
         val data = remoteMessage.data
         if (!isSinchPushPayload(data)) {
             Log.d(TAG, "Non Sinch push payload received. Ignoring.")
             return
         }
         val result = try {
             queryPushNotificationPayload(applicationContext, data)
         } catch (e: Exception) {
             Log.e(TAG, "Error while executing queryPushNotificationPayload", e)
             return
         }
    
         object : ServiceConnection {
             private var callNotificationResult: CallNotificationResult? = null
    
             override fun onServiceConnected(name: ComponentName, service: IBinder) {
                 callNotificationResult?.let {
                     val sinchService = service as SinchService.SinchServiceInterface
                     try {
                         sinchService.relayRemotePushNotificationPayload(it)
                     } catch (e: Exception) {
                         Log.e(TAG, "Error while executing relayRemotePushNotificationPayload", e)
                     }
                 }
                 callNotificationResult = null
             }
    
             override fun onServiceDisconnected(name: ComponentName) {}
    
             fun relayCallNotification(callNotificationResult: CallNotificationResult) {
                 this.callNotificationResult = callNotificationResult
                 createNotificationChannel(NotificationManager.IMPORTANCE_MAX)
                 applicationContext.bindService(
                     Intent(applicationContext, SinchService::class.java), this,
                     BIND_AUTO_CREATE
                 )
             }
         }.relayCallNotification(result)
     }
    Note

    See the implementation of FcmListenerService inside the sinch-rtc-sample-push sample for a complete FCM messaging service covering additional Firebase functionality.

  4. Modify the SinchService binder by adding a method that forwards the push payload to the Sinch client.

    app/src/main/java/com/sinch/rtc/sample/push/SinchService.kt

         fun relayRemotePushNotificationPayload(result: CallNotificationResult) {
             ...
             createClientIfNecessary()
             sinchClient?.relayRemotePushNotification(result)
         }

Listen for incoming calls

  1. Inside the service client creation method, add logic to attach a CallControllerListener:

    app/src/main/java/com/sinch/rtc/demovvsdk/SinchService.kt

     private fun createClient(username: String) {
         // Client creation steps.
         sinchClient?.callController?.addCallControllerListener(SinchCallControllerListener())
     }
  2. Implement the onIncomingCall callback to ask the user whether they want to accept or decline the call:

    app/src/main/java/com/sinch/rtc/sample/push/SinchService.kt

         override fun onIncomingCall(callController: CallController, call: Call) {
             val intent = Intent(applicationContext, IncomingCallScreenActivity::class.java)
                 .apply {
                     putExtra(
                         IncomingCallScreenActivity.EXTRA_ID,
                         IncomingCallScreenActivity.MESSAGE_ID
                     )
                     putExtra(CALL_ID, call.callId)
                 }
             val inForeground = isAppOnForeground(applicationContext)
    
             if (!inForeground) {
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
             } else {
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
             }
    
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !inForeground) {
                 (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(
                     IncomingCallScreenActivity.MESSAGE_ID,
                     createIncomingCallNotification(call.remoteUserId, intent)
                 )
             } else {
                 applicationContext.startActivity(intent)
             }
         }
  3. Build and run the application on both devices. Log in with two different usernames (for example, TestCaller and TestCallee).

  4. On the first device enter TestCallee as the callee name and press CALL.

  5. On the second device you should see a screen asking if you want to accept or decline the call.

Incoming call screen

  1. Press Accept and begin your conversation.

End the call

Once the connection is established users should be able to finish the conversation. Implement this by adding a button visible only after answering the call.

  1. Inside callscreen.xml define a button that is initially hidden:

    app/src/main/res/layout/callscreen.xml

         <com.google.android.material.button.MaterialButton
             android:id="@+id/hangupButton"
             style="@style/Widget.MaterialComponents.Button.Icon"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
             android:layout_centerHorizontal="true"
             android:layout_gravity="center_horizontal"
             android:layout_marginStart="@dimen/margin_average"
             android:layout_marginTop="@dimen/margin_small"
             android:layout_marginEnd="@dimen/margin_average"
             android:padding="@dimen/margin_base_plus"
             android:text="@string/end_call"
             android:textAlignment="center"
             android:textColor="@color/white"
             app:backgroundTint="@color/sinch_decline_red"
             app:icon="@drawable/ic_outline_call_end_24"
             app:iconGravity="textEnd" />
  2. Add the method responsible for handling the hang‑up button click inside CallScreenActivity.

    app/src/main/java/com/sinch/rtc/sample/push/CallScreenActivity.kt

     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         ...
         endCallButton.setOnClickListener { endCall() }
     }
    
     private fun endCall() {
         ...
         call?.hangup()
         finish()
     }
  3. Finally, on the callee side—after answering the call—add a call-specific listener to the call object. In this scenario there are two listeners: one on the caller side (added via callController.addCallControllerListener(SinchCallControllerListener())) and one on the callee side (added after answering).

    app/src/main/java/com/sinch/rtc/sample/push/IncomingCallScreenActivity.kt

     override fun onServiceConnected() {
         val call = call
         if (call != null) {
             // Adding call specific listener.
             call.addCallListener(SinchCallListener())
             remoteUserTextView.text = call.remoteUserId
             if (ACTION_ANSWER == action) {
                 answerClicked()
             } else if (ACTION_IGNORE == action) {
                 declineClicked()
             }
         } else {
             Log.e(TAG, "Started with invalid callId, aborting")
             finish()
         }
     }

Next steps

Now that you've built a simple app to make and receive calls, learn more about the Android SDK.