How to make a native Android app that can block phone calls
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:
- How to detect incoming calls on an Android device?
- Can’t answer incoming call in android marshmallow 6.0
- Android permission doesn’t work even if I have declared it
- End incoming call programmatically
- Is the phone ringing
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>
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();
}
}
}
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();
}
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);
}
}
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;
}
}
}
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 ?
How to make a #native #Android app that can #block phone #calls https://t.co/NMYvOFlPO8
— Nikola Brežnjak (@HitmanHR) February 22, 2018
Thanks for this tutorial. It works perfectly on Android Nougat (25) and earlier, but Oreo (26) and P (27) require the permission MODIFY_PHONE_STATE. This permission isn’t available to third-party apps. Do you know of any solution for blocking calls on Android API level 26 and newer?
Hey Alan, thanks for the comment. Did you find any solution? I must say I didn’t notice this in my testing.
Unfortunately, no. I tried a couple other things, but didn’t have any luck.
As of Android N, Android provides an API called BlockedNumberContract, but this is only available to device vendors, not third party app developers.
I also tried, with no success, the method suggested by the (outdated) blog post below. Even if the suggest method worked, it seems like it would be unreliable.
http://aprogrammersday.blogspot.co.uk/2014/05/disconnect-block-drop-calls-android-4.html
Additionally, Oreo includes a new API for answering incoming calls, but no API is included for ending a call.
https://developer.android.com/reference/android/telecom/TelecomManager#acceptRingingCall()
I’ve created an feature request on Google’s bug tracker. We’ll see where that goes…
Thank you, can you post a link to your feature request? I’d like to upvote it if that’s possible.
Here’s the issue I created:
https://issuetracker.google.com/issues/79961054
After posting the issue, I found another issue with the same feature request posted last month that was closed by Google with a status of “Won’t Fix”. They claim to be working on some sort of call-blocking functionality for a future Android release, but it sounds like a kind of half-baked idea. Here’s the link to that issue:
https://issuetracker.google.com/issues/78000409
Thank you!
Yes, it’s weird they don’t provide a similar API as Apple does with CallKit. Ah well, we’ll wait and see – but I know this new info won’t fly well on my today’s standup 😀
Hello, after some testing, I think it works when CALL_PHONE permission is required at runtime. Many thanks for your effort.
Unfortunately, no. I tried a couple other things, but didn’t have any luck.
As of Android N, Android provides an API called BlockedNumberContract, but this is only available to device vendors, not third party app developers.
I also tried, with no success, the method suggested by the (outdated) blog post below. Even if the suggest method worked, it seems like it would be unreliable.
http://aprogrammersday.blogspot.co.uk/2014/05/disconnect-block-drop-calls-android-4.html
Additionally, Oreo includes a new API for answering incoming calls, but no API is included for ending a call.
https://developer.android.com/reference/android/telecom/TelecomManager#acceptRingingCall()
I’ve created an feature request on Google’s bug tracker. We’ll see where that goes…
Good news! In Android P, the the endCall method in TelecomManager is now a public API. I’ve tested with the latest Android beta, and was able to block an incoming call. I had to add a permission to the manifest and the permission prompt to get the incoming phone number:
android.permission.READ_CALL_LOG
Sadly, I don’t know that we’ll ever have a solution for Oreo.
If you’re curious, this is the commit where the change was made:
https://android-review.googlesource.com/c/platform/frameworks/base/+/681559
Here’s my updated endCall method:
private void endCall(Context context) {
if (Build.VERSION.SDK_INT >= 28) {
TelecomManager telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
if (telecomManager != null)
telecomManager.endCall();
} else if (Build.VERSION.SDK_INT >= 26) {
// No apparent solution for Oreo.
} else {
ITelephony iTelephony = getITelephony(context);
if (iTelephony != null)
iTelephony.endCall();
}
}
Forgot to mention that this also requires the android.permission.ANSWER_PHONE_CALLS permission.
It may as well show that this was maaaaybe an oversight on their end and that they’ll fix this. #letsHope 🙂
Hey Allen,
I get null incomingNumber for :
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
my permission settings as: var permissions = arrayOf(android.Manifest.permission.ANSWER_PHONE_CALLS,
android.Manifest.permission.READ_PHONE_STATE
, android.Manifest.permission.CALL_PHONE)
Would you know why?
The problem with getting a null incomingNumber comes from the lack of READ_CALL_LOG permission which you have to specifically request due to changes in Android 9 (https://developer.android.com/about/versions/pie/android-9.0-changes-all).
Basically, if you’re missing that permission, the phone number you get will always be null.
Hey Alan,
That’s awesome! Thank you very much for updating me on this!
Thanks so much for this comment. Saved the day for me 🙂
Hey Alan,
Thanks so much for the updates on this thread. So far it seems you have found solutions for most of the SDK versions besides 26 & 27. After doing some digging on my own, I am happy to say I found a solution that works for me and does end the call for Oreo.
For this to work, you have to add and request the CALL_PHONE permission. Below is the code …
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager != null) {
try {
telephonyManager.getClass().getMethod(“endCall”).invoke(telephonyManager);
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
I am making use of the TelephonyManager and using reflection to invoke the endCall method it has. I hope that works for you as well and we can finally put this bug to bed 🙂
Hi, Jay. Thanks for the update.
I gave your suggestion a try, but when using reflection to call endCall, an exception is thrown that says the MODIFY_PHONE_STATE permission is required. This is the same permission issue I’ve run into before with SDK 26 & 27, since MODIFY_PHONE_STATE isn’t available to third-party applications. I’m requesting the CALL_PHONE permission, but it doesn’t seem to have any effect. I’m testing using an Android simulator running SDK 26.
Was there anything else you had to do to get this to work on 26 and 27?
Hey guys,
Also recently came across this issue, regarding ending the call on Oreo.
The problem is that it works on one machine, and on the other it doesn’t. All while using the SAME emulator image to test it.
I’m requesting the CALL_PHONE permission and it ends the call, but not on all test machines with the same emulator image.
This is getting really frustrating because it’s hindering the intended app functionality.
Hey Alan. Nothing else. I tried it with a real device as opposed to an emulator though so you might want to explore that as well. Below are my list of permissions you might want to try requesting:
READ_CALL_LOG (I need this for my app as we display phone call entries)
READ_PHONE_STATE
CALL_PHONE
PROCESS_OUTGOING_CALLS
ANSWER_PHONE_CALLS (I only request this for Android Pie like you earlier mentioned)
I hope this makes a difference.
Thanks for all of the leg work on this!
I’m looking for a way to whitelist callers in android P. I can’t find an app in the play store. or elsewhere, that works (probably because of these API changes you mention above). Did you ever find anything that works out of the box?
Hi,
“(red squiggly dots under this package name)”
Just place ITelephony.class to com/android/internal/telephony path and there aren’t red dots under package name.
Regarding call blocking on Android Oreo… Actually didn’t see this problem, probably because the ITelephony.aidl is located under com.android.internal.telephony package.
Only time I encountered the “MODIFY_PHONE_STATE” issue was on Android P but as you Alan already said, you have to use their provided API to actually end the call since you can’t use reflection any more for non-SDK related methods.
Basically this is a list of permissions that I use that work on every Android version I tested on, and thats Lollipop and up (including Pie).
Obviously you have to request them during runtime due to changes made in API level 23, but you know that already.
Hey Alan,
Thanks so much for the updates on this thread. So far it seems you have found solutions for most of the SDK versions besides 26 & 27. After doing some digging on my own, I am happy to say I found a solution that works for me and does end the call for Oreo.
For this to work, you have to add and request the CALL_PHONE permission. Below is the code …
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager != null) {
try {
telephonyManager.getClass().getMethod(“endCall”).invoke(telephonyManager);
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
I am making use of the TelephonyManager and using reflection to invoke the endCall method it has. I hope that works for you as well and we can finally put this bug to bed 🙂
hi
i used a trick to end out going call
i have tested it till N.
but you people can try this .if it works.
use this code in your broadcastreciver class where you want to end call
setResultData(null)
Hello guys. do you have any idea on how to block ussd code dialing as well? as long as i am aware, dialing ussd is a CALL action but i can not manage to block them in a real device using your suggested approaches.
tnx in advance.
Hi
The endCall is deprecated. and please advise if any alternative for android 10?
Hello, Do you have a solution with the new SDK?