# 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](/docs/in-app-calling/getting-started/android/make-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](https://developers.sinch.com/docs/in-app-calling/android/push-notifications/). This guide uses FCM. Ensure you have generated and downloaded your `google-services.json` file. 1. Copy your `google-services.json` file into the `/app/` directory. 2. Create a `FcmListenerService` class that extends `FirebaseMessagingService`. Declare the service inside `AndroidManifest.xml`. `app/src/main/AndroidManifest.xml` ```xml ``` 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` ```kotlin 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) } ``` 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` ```kotlin 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` ```kotlin 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` ```kotlin 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](/assets/sc1.b1d6489975f234cb4463e52ab2b3ccc064b4d65bbbc4957e3e73b3a558be8584.4503b5b1.jpg) 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` ```xml ``` 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` ```kotlin 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` ```kotlin 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](https://developers.sinch.com/docs/in-app-calling/android-cloud/).