Broadcast Receiver

A broadcast receiver is an Android component that listen for system or application event. Such an event can be, for example, information about a new SMS, an incoming or outgoing call, or a low battery.

With the help of broadcast receiver I will show how to detect incoming phone call in Android app.

Create Broadcast Receiver

There are two way to register and create a broadcast receiver.

First way is register in application code.

class MainActivity : AppCompatActivity() {

    private val filter = IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)

    private val broadcast = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {

        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(broadcast, filter)
    }

    override fun onPause() {
        super.onPause()
         unregisterReceiver(broadcast)
    }
}

Registration in the application code means that our application listens only when it is active.

In the second way, our BroadcastReceiver will be launched even when the application is currently inactive, i.e. we are listening continuously. For detecting incoming calls, the option below will be a better choice.

Create new class that extend the BroadcastReceiver class and implement the onReceive() method.

class CallReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, p1: Intent?) {
    }
}

Then the broadcast receiver should be registered in AndroidManifest.xml file.

<receiver android:name=".CallReceiver" >
<intent-filter>
    <action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>

Permission

Give read phone state permission and read call log permission in AndroidManifest.xml file.

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>

Read Phone State Listener

In method onReceive create PhoneStateListener.

class CallReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, p1: Intent?) {
        val telephonyManager: TelephonyManager = context?.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager;
        telephonyManager.listen(object: PhoneStateListener(){
            override fun onCallStateChanged(state: Int, phoneNumber: String?) {
                super.onCallStateChanged(state, phoneNumber)
            
            }
        }, PhoneStateListener.LISTEN_CALL_STATE);
    }
}

When new call come the onCallStateChanged method called.

Below is explained what the parameters of onCallStateChanged mean.

CALL_STATE_IDLE – no activity

CALL_STATE_RINGING – Ringing. A new call arrived and is ringing or waiting. In the latter case, another call is already active.

CALL_STATE_OFFHOOK – Off-hook. At least one call exists that is dialing, active, or on hold, and no calls are ringing or waiting.

class CallReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, p1: Intent?) {
        val telephonyManager: TelephonyManager = context?.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager;
        telephonyManager.listen(object: PhoneStateListener(){
            override fun onCallStateChanged(state: Int, phoneNumber: String?) {
                super.onCallStateChanged(state, phoneNumber)
                when (state) {
                    TelephonyManager.CALL_STATE_IDLE -> {
                        Log.i(TAG,"not in call")
                    }
                    TelephonyManager.CALL_STATE_RINGING -> {
                        Log.i(TAG, "ringing")
                    }
                    TelephonyManager.CALL_STATE_OFFHOOK -> {
                        Log.i(TAG, "call is dialing, active or on hold")
                    }
                }
            }
        }, PhoneStateListener.LISTEN_CALL_STATE);
    }
}

How does the state change with an incoming call?

When it rings, goes from state IDLE to RINGING. Then when it is answered it changes to OFFHOOK, to IDLE when it hung up. To register changes, you need to create additional variables.

class ServiceReceiver : BroadcastReceiver() {

    var laststate: Int = 0
    var isIncoming: Boolean = false

    override fun onReceive(context: Context?, p1: Intent?) {
        val telephonyManager: TelephonyManager = context?.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager;
        telephonyManager.listen(object: PhoneStateListener(){
            override fun onCallStateChanged(state: Int, phoneNumber: String?) {
                super.onCallStateChanged(state, phoneNumber)
                if (state == TelephonyManager.CALL_STATE_RINGING) {
                    isIncoming = true
                }
                if (state == TelephonyManager.CALL_STATE_OFFHOOK && laststate != TelephonyManager.CALL_STATE_RINGING) {
                    isIncoming = false
                }
                if (state == TelephonyManager.CALL_STATE_IDLE && laststate == TelephonyManager.CALL_STATE_OFFHOOK) {
                   Log.i(TAG, "incoming call end, phone number: $phoneNumber")
                }
                laststate = state
            }
        }, PhoneStateListener.LISTEN_CALL_STATE);
    }
}

On outgoing calls, states change differently. Goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up. So outgoing calls can be detected in a similar way.