DEV Community

Nikola Brežnjak
Nikola Brežnjak

Posted on

How to make a native Android app that can block phone calls

Originally published on my blog.

TL;DR

In this post, I'll show you step by step how to make a native Android app that can block certain numbers from calling you.

The source code is on Github.

I hope that my step by step guide that I'm going to show you here will help you and save you from doing additional research.

Of course, since I'm not a native Android developer in my day to day job, I'm doing it also for the fact that it will serve me as a good reminder for when I need to deal with a similar situation again. Shout out to the rest of you #jackOfAllTrades out there 💪

Also, given the statement above; I would appreciate any feedback regarding this code. 🙏

!TL;DR

I've spent a lot of time going through StackOverflow and blog posts in search of this solution. Of all of those, these were helpful:

But sadly, none of them was straightforward, beginner kind of tutorial. So, after a lot of additional research, I made it work, and here's my best attempt at explaining how.

As a sidenote: while testing this, the discovery of how to simulate an incoming call or SMS to an emulator in Android Studio was also very helpful.

Starting a new project

In Android Studio go to File->New->New Project, give it a name and a location and click Next:

Leave the default option for minimum API level:

Select an Empty Activity template:

Leave the name of the activity as is:

AndroidManifest.xml

Set the permissions (two uses-permission tags) and the receiver tags in AndroidManifest.xml file:



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.nikola.callblockingtestdemo">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver  android:name=".IncomingCallReceiver" android:enabled="true" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE" />
            </intent-filter>
        </receiver>
    </application>
</manifest>


Enter fullscreen mode Exit fullscreen mode

With the READ_PHONE_STATE permission we get this (as defined in official docs):

Allows read-only access to phone state, including the phone number of the device, current cellular network information, the status of any ongoing calls, and a list of any PhoneAccounts registered on the device.

With the CALL_PHONE permission we get this (as defined in official docs):

Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call.

⚠️ I found that even though not stated here, I need this permission so that I can end the call programmatically.

The receiver tag is used to define a class that will handle the broadcast action of android.intent.action.PHONE_STATE. Android OS will broadcast this action when, as the name implies, the state of the phone call changes (we get a call, decline a call, are on the call, etc.).

IncomingCallReceiver.java

Create a new class (File->New->Java Class), call it IncomingCallReceiver and paste this code in (note: your package name will be different than mine!):



package com.example.nikola.callblockingtestdemo;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.widget.Toast;
import java.lang.reflect.Method;
import com.android.internal.telephony.ITelephony;

public class IncomingCallReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        ITelephony telephonyService;
        try {
            String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);

            if(state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_RINGING)){
                TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
                try {
                    Method m = tm.getClass().getDeclaredMethod("getITelephony");

                    m.setAccessible(true);
                    telephonyService = (ITelephony) m.invoke(tm);

                    if ((number != null)) {
                        telephonyService.endCall();
                        Toast.makeText(context, "Ending the call from: " + number, Toast.LENGTH_SHORT).show();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }

                Toast.makeText(context, "Ring " + number, Toast.LENGTH_SHORT).show();

            }
            if(state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_OFFHOOK)){
                Toast.makeText(context, "Answered " + number, Toast.LENGTH_SHORT).show();
            }
            if(state.equalsIgnoreCase(TelephonyManager.EXTRA_STATE_IDLE)){
                Toast.makeText(context, "Idle "+ number, Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

In Android, if we want to 'get' the data from the BroadcastReceiver, we need to inherit the BroadcastReceiver class, and we need to override the onReceive method. In this method, we're using the TelephonyManager to get the state of the call, and we're using the ITelephony interface to end the call.

To be honest, this is where it gets a bit 'weird', as to get this ITelephony interface, you need to create the ITelephony interface.

ITelephony.java

To do that, create a new class (File->New->Java Class), call it ITelephony and paste this code in (note: overwrite everything with the below content; yes, even the weird package name):



package com.android.internal.telephony;

public interface ITelephony {
    boolean endCall();
    void answerRingingCall();
    void silenceRinger();
}


Enter fullscreen mode Exit fullscreen mode

Android Studio will complain about package com.android.internal.telephony; (red squiggly dots under this package name), but that's how it has to be set for this to work. I didn't find the exact explanation why this has to be included, so if you know, please share it in the comments.

Requesting permissions at runtime

This was one thing that was hindering my success in getting this to work!

Namely, after Android 6.0+, even if you have permissions set in the AndroidManifest.xml file, you still have to explicitly ask the user for them if they fall under the category of dangerous permissions. This is the list of such permissions:

  • ACCESS_COARSE_LOCATION
  • ACCESS_FINE_LOCATION
  • ADD_VOICEMAIL
  • BODY_SENSORS
  • CALL_PHONE
  • CAMERA
  • GET_ACCOUNTS
  • PROCESS_OUTGOING_CALLS
  • READ_CALENDAR
  • READ_CALL_LOG
  • READ_CELL_BROADCASTS
  • READ_CONTACTS
  • READ_EXTERNAL_STORAGE
  • READ_PHONE_STATE
  • READ_SMS
  • RECEIVE_MMS
  • RECEIVE_SMS
  • RECEIVE_WAP_PUSH
  • RECORD_AUDIO
  • SEND_SMS
  • USE_SIP
  • WRITE_CALENDAR
  • WRITE_CALL_LOG
  • WRITE_CONTACTS
  • WRITE_EXTERNAL_STORAGE

To ask for such permissions here's the code you can use (I used it in MainActivity.java in the onCreate method):



if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
    if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED || checkSelfPermission(Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED) {
        String[] permissions = {Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE};
        requestPermissions(permissions, PERMISSION_REQUEST_READ_PHONE_STATE);
    }
}


Enter fullscreen mode Exit fullscreen mode

The PERMISSION_REQUEST_READ_PHONE_STATE variable is used to determine which permission was asked for in the onRequestPermissionsResult method. Of course, if you don't need to execute any logic depending on whether or not the user approved the permission, you can leave out this method:



@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_READ_PHONE_STATE: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission granted: " + PERMISSION_REQUEST_READ_PHONE_STATE, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Permission NOT granted: " + PERMISSION_REQUEST_READ_PHONE_STATE, Toast.LENGTH_SHORT).show();
}

        return;
    }
}
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




App in action

This is how the app looks like in action, tested on the emulator and call triggered by using Android Device Monitor in Android Studio:

Conclusion

In this post, I showed you how to make a native Android app that can block certain numbers from calling you. I pointed out the blocker that I was facing, and I'm still searching a solution to hide a native incoming call popup that still sometimes shows up for a brief second before the call gets rejected.

So, if you have any ideas, I'm open to suggestions 💪

Top comments (28)

Collapse
 
nikhiljugale007 profile image
Nikhil Subhash Jugale

It is not blocking the call for me..
Th ring continue to ring until i cut the phone.
I am using exact same code from github.
And testing on Android studio emulator pixel 2.

Can anyone please help...

Collapse
 
tmjansson profile image
Mikael Jansson

Just for info.
Works like charm on my Moto G3 runnnig V6.0.1 Android.

But on Moto G7 Power running Android V9, I don't get caller ID and I get:

  • java.lang.SecurityException: MODIFY_PHONE_STATE permission required.
Collapse
 
ishan001 profile image
Ishan Khandelwal

Use TelecomManger to end call programatically for Android 9 and above using ANSWER_PHONE_CALL permission.

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
TelecomManager tm = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) {
success = tm.endCall();
Log.d("call state", "call end");
}
}

Collapse
 
kaushal7171 profile image
kaushal raykar • Edited

hii,its work fine, but it's block the calls even if we close the application

Collapse
 
nikola profile image
Nikola Brežnjak

What are you referring to with 'destroyed'?

Collapse
 
kaushal7171 profile image
kaushal raykar • Edited

i want to de-register the broadcast receiver after press the back button.that is once i exit from app blocking should not be happen.

Thread Thread
 
nikola profile image
Nikola Brežnjak

And what have you tried so far?

Collapse
 
adibacco profile image
adibacco • Edited

It is not working on Motorola G5S plus. What can be wrong? Permissions are granted but incoming call is not blocked. G5S plus is running android 7.1.1 API level 25

Collapse
 
nikola profile image
Nikola Brežnjak

What do you get in the logs when you debug the app?

Collapse
 
adibacco profile image
adibacco

I get a call notification that disappears after less of a second. Sometimes the notification will not pop-up at all. But it is still useful. Thx

Thread Thread
 
nikola profile image
Nikola Brežnjak

This actually means it's working for you.

Collapse
 
surbhivsambare profile image
SurbhiVSambare

I want the calls should get blocked .. how I can achieve that..?

Collapse
 
nikola profile image
Nikola Brežnjak

What have you tried so far?

Collapse
 
surbhivsambare profile image
SurbhiVSambare

Thank you for your response.. I want to block the outgoing calls.. when my application is going in background.. I have written the following code with the help of stackoverflow but the code is not working:-

Will be waiting for your response.. thank you in advance.

public class OutgoingCallBlocking extends BroadcastReceiver {

String number;

@SuppressLint("WrongConstant")
@Override
public void onReceive(Context context, Intent intent) {
    number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
    setResultData(null);
    Toast.makeText(context, "Outgoing Call Blocked "+ number , 5000).show();
}

}

Collapse
 
namnhong profile image
nam giang

Hi Nick, does this work in the background when the app is off?

Thanks

Collapse
 
nikola profile image
Nikola Brežnjak

Yes.

Collapse
 
zoro238 profile image
zoro238

Now this is when there is an incoming call, but when there is an outgoing call and I do not want the outgoing number to appear. How will this be done

Collapse
 
nikola profile image
Nikola Brežnjak

What have you tried so far?

Collapse
 
tejavooh profile image
Teja Vu

Yay! Finally, someone broke the jinx of the code, even I spent a lot of time on StackOverflow, for this, thank you Nikola!

Works like a charm!

Collapse
 
nikola profile image
Nikola Brežnjak

Great, I'm glad this helped you 👍

Collapse
 
itamarmosh profile image
itamarmosh

when I get a call, instead of closing the call my app gets closed. AND the part that says "PERMISSION_REQUEST_READ_PHONE_STATE" is in red. any help?

Collapse
 
ruthvik47 profile image
Ruthvik47

It won't work,it will ask MODIFY_PHONE_STATE permission to block the calls.Now android is not allowing MODIFY_PHONE_STATE permissions to third party applications.

Collapse
 
ishan001 profile image
Ishan Khandelwal

Use TelecomManger to end call programatically for Android 9 and above using ANSWER_PHONE_CALL permission.

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
TelecomManager tm = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) {
success = tm.endCall();
Log.d("call state", "call end");
}
}

Collapse
 
bajithelearner profile image
Baji Shaik

Exactly, did you find any solution for this?

Collapse
 
ruthvik47 profile image
Ruthvik47

Nope

Some comments may only be visible to logged-in visitors. Sign in to view all comments.