Detecting and rejecting incoming phone calls on Android

In this article we are going to explore how we can detect, intercept and reject incoming phone calls on different Android versions.

At the bottom of this article you’ll find sample code, so you can check it in case if you have some problems with following the steps described in this article.

So, let’s start by adding the appropriate permissions in Manifest file:

The class that will be responsible for intercepting and then answering on the phone call request is: ‘CallScreeningService’, from ‘android.telecom’ package.

It’s a android service that allows us to make a default dialer app (which we won’t create in this article since it isn’t necessary for phone call intercepting) and it also gives us a possibility to allow or disallow incoming calls before they are displayed to the user. So, we need to extend this service in a new class (called ‘MyCallScreeningService’) in order to get call details via ‘onScreenCall’ callback method.

In ‘MyCallScreeningService’ we can see that we are overriding ‘onScreenCall’ method which contains informations about incoming phone call. Via ‘getPhoneNumber’ method we are getting formatted incoming phone number. Method ‘handlePhoneCall’ handles the incoming phone call. In this method we are checking if incoming phone call number is equal to the phone number that’s forbidden and in this case, we are rejecting the call, by applying the following properties to ‘CallResponse.Builder’:

setRejectCall(true)
setDisallowCall(true)
setSkipCallLog(false)

With this properties and with original call details object, we are invoking ‘respondToCall’ method, which is responsible for all the hard work of handling incoming call. Method ‘displayToast’ is displaying simple Toast message, from ‘NotificationManager’ class. If you are planning to use some DI library, like Hilt, you can inject this class into ‘MyCallScreeningService’.

Custom EventBus class is used for sending events (i.e. ‘MessageEvent’) to main activity, so the events can be displayed in a main activity log:

class MessageEvent(val message: String) {}

In order to use EventBus library, we need to import it in app ‘build.gradle’ file:

implementation 'org.greenrobot:eventbus:3.2.0'

and for logging we are going to use Timber library, so we need also to add the following dependency:

implementation 'com.jakewharton.timber:timber:4.7.1'

We are using two extension methods ‘removeTelPrefix’ and ‘parseCountryCode’ in order to format incoming phone call string into some nicer format:

fun String.removeTelPrefix() = this.replace(TEL_PREFIX, "")
fun String.parseCountryCode(): String = Uri.decode(this)

After we have created our custom ‘CallScreeningService’ implementation, we need to register this service in manifest:

It’s important to put appropriate permission (‘android.permission.BIND_SCREENING_SERVICE’) to the service and appropriate intent-filter, so our call screening service will work as expected.

In manifest file, under the main activity section, we need to add two intent filters:

In both intent filter we are specifying that main activity will handle ‘android.intent.action.DIAL’ action. The action that is invoked when incoming call is placed.

In order to display Toast messages, we are going to build a ‘NotificationManagerImpl’ class that provides a method for displaying Toast notification:

Very nice, so far we have created the most important, base setup steps for intercepting phone call functionality.

We’ll resume on creating main activity functionality. In main activity we need to implement the following functionalities:

Don’t worry about a lot new classes that are used in main activity. In next paragraph, we are going to explain for what are they used.

The layout for main activity is very simple:

Dimension values used for main layout:

String values used in main layout and other classes:

At the top of ‘build.gradle’, in ‘plugins’ section add:

id 'kotlin-android-extensions'

so we can use Kotlin extensions.

A class called ‘ManifestPermissionRequesterImpl’ is a class that holds all the required functionalities for requesting manifest permissions. We could put the functionality for invoking manifest permissions in main activity, without using this class, but it’s used because it makes code in main activity more cleaner.

In ‘ManifestPermissionRequesterImpl’, we need to specify WeakReference variable to the activity from which we will invoke requesting manifest permissions. For requesting manifest permissions we’ll be using ‘SimplePermissions’ library, so we’ll have to add appropriate gradle dependency:

implementation 'pub.devrel:easypermissions:3.0.0'

and implement ‘EasyPermissions.PermissionCallbacks’ callbacks. In each callback method we are sending appropriate SingleLiveData event to main activity, so main activity can handle next actions. If ‘READ_PHONE_STATE’ permission is granted, ‘PhoneManifestPermissionsEnabled’ event is sent to the main activity. We can see that ‘listenForUiEvents’ method handles receiving different messages sent via uiEvent LiveData variable:

If user has accepted ‘READ_PHONE_STATE’ permission dialog, we are invoking ‘invokeCapabilitiesRequest’ method from ‘CapabilitiesRequestorImpl’ class that displays a dialog where user must select current app as default dialer app. This functionality is mandatory only on android versions equal or bigger than Android Q.

In ‘CapabilitiesRequestorImpl’ we must specify variable that will contain weak reference to activity that’s invoking the dialer capability. Method ‘invokeCapabilitiesRequest’ starts the functionality of checking if phone dialer functionality really needs to be enabled or not.

Method ‘requestDialerPermission’ displays a dialer selection dialog by invoking activity extension method ‘startCallScreeningPermissionScreenon Android Q and method ‘startSelectDialerScreen’ invokes displaying dialer selection popup on android versions lower than Android Q. Extension method ‘hasDialerCapability’ is responsible for checking if dialer capability is needed in order to intercept incoming phone calls:

If our app is set as default dialer app, event ‘PhoneCapabilityEnabled’ will be send to main activity. Don’t worry if we didn’t specified dialer UI layout, it’s not mandatory to implement this functionality if we only want to intercept phone calls. If user didn’t set current app as dialer app, method ‘displayCallScreeningPermissionDialog’ will be invoked showing message that you need to set our app as default android dialer app.

Now, we are going to implement ‘BaseActivity’, which is the abstract class that our main activity extends.

It contains only one variable ‘uiEvent’ which is a ‘SingleLiveEvent’, a basically LiveData object that can be consumed only once.

In ‘UiEvent’ class we have defined all the events that can be sent to LiveData object of ‘BaseActivity’ and our main activity is listening for events received in this LiveData object.

The last step of our application is to specify some constants:

const val TEL_PREFIX = "tel:"
const val FORBIDDEN_PHONE_CALL_NUMBER = "6505551212"

Now we can try to make an incoming call from ‘6505551212’ and we should see that this phone call is rejected. All other phone calls should be allowed.

In this article have learned about ‘CallScreeningService’ class, that allows us to intercept and reject incoming phone calls. We have learned how it works and what should we specify in order to listen for incoming phone calls. :)

Source code of sample app built in this article:

Web & mobile application developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store