Reputation: 87
I'm trying to block Incoming calls from specific numbers, I managed to detect the imcoming call and its number. However, I have trouble to block the call. I went throught all the code I've found in this site and in others, I'm beggining to think that maybe the code doesnt work due to new versions of android or the core differences of each device (I own LG4).
One more think, can anyone please explain me the importance of creating the ITelephony in this package:
package com.android.internal.telephony;
This is my code:
package com.darkmoonapps.telephony;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.widget.Toast;
import com.android.internal.telephony.ITelephony;
import java.lang.reflect.Method;
/**
* Created by Shai on 19/12/2017.
*/
public class incomingCalls 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();
}
} 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();
}
}
}
This is my Telephony:
package com.android.internal.telephony;
/**
* Created by Shai on 19/12/2017.
*/
public interface ITelephony {
boolean endCall();
void answerRingingCall();
void silenceRinger();
}
Thanks!
Upvotes: 4
Views: 3978
Reputation: 1
The method provided by @Nikola doesn't work anymore since starting from Android 9 the platform has completely disallowed the usage of ITelephony
by normal apps. Any attempt to get the ITelephony
instance via reflection will cause an exception to be thrown.
After some research, I found the CallScreeningService
might come to rescue. You can use the offical api to silence and reject incoming calls without any notification or call log being displayed by the system.
Upvotes: 0
Reputation: 15048
I was facing the same problem as you. I found the solution and documented it step by step on my blog.
Here's the most important part that I was missing: 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.
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);
}
}
Here's the post in full content:
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. 🙏
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.
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:
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.).
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.
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.
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:
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;
}
}
}
This is how the app looks like in action, tested on the emulator and call triggered by using Android Device Monitor in Android Studio:
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 💪
Upvotes: 5