Handling incoming calls

In the previous section we added the capability of initiating the call. This section describes how to receive notifications and handle incoming calls.

If you haven't yet initiated a call, go back and follow the steps in that section.

Note:

To test if the call succeeded and audio connection is established, it's best to have 2 physical Android devices however you can initiate the call on the emulator.

Setting up FCM push notifications

To receive notifications about incoming calls, you must set up your application to work with either FCM or HMS Push Notification Platform. Information about how to add this capability and comparison of these platforms can be found here. For this step-by-step guide we showcase FCM and assume that you have your google-services.json file generated and downloaded.

  1. Copy your 'google-services.json' file to <your android application name>/app/ directory.
  2. Create FCMService class that extends FirebaseMessagingService. Declare the service inside AndroidManifest.xml file.

    app/src/main/AndroidManifest.xml

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

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

    Copy
    Copied
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        val data = remoteMessage.data
        if (SinchHelpers.isSinchPushPayload(data)) {
            object : ServiceConnection {
                private var payload: Map<String, String>? = null
                override fun onServiceConnected(name: ComponentName, 
                        service: IBinder) {
                    payload?.let {
                         (service as SinchService.SinchServiceBinder).
                            relayRemotePushNotificationPayload(
                        it
                        )
                    }
                    payload = null
                }
    
                override fun onServiceDisconnected(name: ComponentName) {}
                fun relayMessageData(data: Map<String, String>?) {
                    payload = data
                    applicationContext.bindService(
                        Intent(
                            applicationContext,
                            SinchService::class.java
                        ), this, BIND_AUTO_CREATE
                    )
                }
            }.relayMessageData(data)
        }
    }
    Note:

    This implementation of FCMService shows only most basic message handling use case. For more advanced usage check sinch-rtc-sample-push sample.

  4. Modify SinchService binder by adding method forwarding push payload to Sinch client.

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

    Copy
    Copied
    inner class SinchServiceBinder : Binder() {
        
        fun relayRemotePushNotificationPayload(map: Map<String, String>) {
            sinchClient?.relayRemotePushNotificationPayload(map)
        }
    
    }

Listening for incoming calls

  1. Inside the Service binder class add a method responsible for adding a CallClientListener to call client:

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

    Copy
    Copied
    fun addCallClientListener(callClientListener: CallClientListener) {
       sinchClient?.callClient?.addCallClientListener(callClientListener)
    }
    
    fun removeCallClientListener(callClientListener: CallClientListener) {
       sinchClient?.callClient?.removeCallClientListener(callClientListener)
    }
  2. Once connected to the service add LoggedInActivity as a CallClientListener

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

    Copy
    Copied
    override fun onServiceConnected(p0: ComponentName?, binder: IBinder?) {
       serviceBinder = binder as SinchService.SinchServiceBinder?
       serviceBinder?.addCallClientListener(this)
    }
  3. Implement onIncomingCall callback asking user if he wants to accept or decline the call:

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

    Copy
    Copied
    override fun onIncomingCall(p0: CallClient?, call: Call?) {
       Log.d(TAG, "onIncomingCall $call")
       AlertDialog.Builder(this).setMessage("${call?.remoteUserId} calling")
           .setPositiveButton("Accept") { _, _ ->
               call?.answer()
           }
           .setNegativeButton("Decline") { _, _ ->
               call?.hangup()
           }.show()
    }
  4. Build and run the application on both devices. Log into the application providing 2 different usernames (TestCaller and TestCallee).
  5. On the first device enter TestCallee as the 'callee name' and press CALL .
  6. On the second device you should see a popup asking if you want to accept or decline the call.

    sc1

  7. Press Accept and begin your conversation!

Ending the call

Once the connection is established users should be able to finish the conversation. We will implement this feature by adding a simple button visible only after answering the call.

  1. Inside activity_logged_in.xml file define a button that stays initially hidden:

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

    Copy
    Copied
    <Button
       android:id="@+id/hangupButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       android:visibility="gone"
       android:text="hangup"/>
  2. Create 2 methods responsible for handling hangup button state inside LoggedInActivity

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

    Copy
    Copied
    private fun setupHangupButton(call: Call?) {
       binding.hangupButton.isVisible = true
       binding.hangupButton.setOnClickListener {
           call?.hangup()
       }
    }
    
    private fun disableHangupButton() {
       binding.hangupButton.apply {
           isVisible = false
           setOnClickListener(null)
       }
    
    }
  3. Modify LoggedInCallListener we defined in the earlier step. Once the call is established setup the hangup button logic, and once it's ended disable it:

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

    Copy
    Copied
    override fun onCallEstablished(call: Call?) {
       Log.d(TAG, "onCallEstablished")
       setupHangupButton(call)
    }
    
    override fun onCallEnded(call: Call?) {
       Log.d(TAG, "onCallEnded ${call?.details?.endCause}")
       disableHangupButton()
    }
  4. Finally on the callee side, after answering the call we need to add an instance of the call listener to the call object itself. It's important to understand that in that case there are 2 call listeners alive. First one at the caller site (added via serviceBinder?.addCallClientListener(this) method invocation) notifying about state of outgoing call and the second one, on the callee site added once the call is answered:

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

    Copy
    Copied
    AlertDialog.Builder(this).setMessage("${call?.remoteUserId} calling")
       .setPositiveButton("Accept") { _, _ ->
           call?.answer()
           // Adding second CallListener instance
           call?.addCallListener(LoggedInCallListener())
       }
       .setNegativeButton("Decline") { _, _ ->
           call?.hangup()
       }.show()

Next steps

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

Was this page helpful?