如何开发一款可以拦截电话的原生安卓应用
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
原文发表于我的博客。
太长不看
在这篇文章中,我将一步一步地向您展示如何制作一个原生 Android 应用,该应用可以阻止某些号码拨打您的电话。
源代码在Github上。
我希望我接下来要展示的这些分步指南能够帮助你,让你省去额外的研究时间。
当然,由于我的日常工作并非安卓原生开发,我这样做也是为了在以后遇到类似情况时能有个很好的参考。向其他身兼多职的#jackOfAllTrades 们致敬💪
另外,鉴于以上情况,我非常感谢您对这段代码的任何反馈。🙏
太长不看
我花了很多时间浏览 StackOverflow 和博客文章来寻找这个问题的解决方案。其中,以下这些很有帮助:
但遗憾的是,这些教程都不是那种简单易懂、适合初学者的。所以,经过大量的额外研究,我最终成功实现了这个功能,以下是我尽力解释的方法。
顺便提一下:在测试过程中,发现如何在 Android Studio 中模拟来电或短信到模拟器也非常有用。
启动一个新项目
在 Android Studio 中,转到File->New->New Project“设置”,为其命名并指定位置,然后单击Next:
保留最低 API 级别的默认选项:
选择Empty Activity模板:
活动名称保持不变:
AndroidManifest.xml
设置权限(两个uses-permission标签)和文件receiver中的标签AndroidManifest.xml:
<?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>
获得授权后,READ_PHONE_STATE我们可以获得以下权限(如官方文档中所定义):
允许对手机状态进行只读访问,包括设备的电话号码、当前蜂窝网络信息、任何正在进行的通话状态以及设备上注册的任何电话帐户列表。
获得授权后,CALL_PHONE我们可以获得以下权限(如官方文档中所定义):
允许应用程序无需通过拨号器用户界面让用户确认呼叫即可发起电话呼叫。
⚠️ 我发现,虽然这里没有说明,但我需要这个权限才能以编程方式结束通话。
该receiver标签用于定义一个类,该类将处理广播操作android.intent.action.PHONE_STATE。顾名思义,Android 操作系统会在电话通话状态发生变化时(例如接到电话、拒接电话、正在通话等)广播此操作。
IncomingCallReceiver.java
创建一个新的类(File->New->Java Class),将其命名为IncomingCallReceiver,并将以下代码粘贴到其中(注意:你的package名字要和我的不一样!):
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();
}
}
}
在 Android 中,如果我们想从某个对象获取数据BroadcastReceiver,我们需要继承BroadcastReceiver该类并重写相应的onReceive方法。在这个方法中,我们使用某个对象TelephonyManager来获取通话状态,并使用ITelephony接口来结束通话。
说实话,这里有点“奇怪”,因为要获得这个ITelephony界面,你需要先创建这个ITelephony界面。
ITelephony.java
为此,请创建一个新类(File->New->Java Class),将其命名为ITelephony,并将以下代码粘贴到其中(注意:请用以下内容覆盖所有内容;是的,甚至包括奇怪的包名):
package com.android.internal.telephony;
public interface ITelephony {
boolean endCall();
void answerRingingCall();
void silenceRinger();
}
Android Studio 会报错package com.android.internal.telephony;(包名下方会出现红色波浪线),但这是必须这样设置的才能正常工作。我没找到具体原因,如果您知道,请在评论区分享。
在运行时请求权限
这是阻碍我成功完成这项工作的一个因素!
也就是说,在 Android 6.0 及更高版本中,即使您在AndroidManifest.xml文件中设置了权限,如果这些权限属于危险权限,您仍然需要明确地向用户征求意见。以下是此类权限的列表:
- ACCESS_COARSE_LOCATION
- 访问精细位置
- 添加语音信箱
- 身体传感器
- 拨打电话
- 相机
- 获取帐户
- 处理外拨电话
- 读取日历
- 读取通话日志
- 读取单元广播
- 读取联系人
- 读取外部存储
- 读取手机状态
- 读取短信
- 接收彩信
- 接收短信
- 接收 WAP 推送
- 录制音频
- 发送短信
- USE_SIP
- 写入日历
- 写入调用日志
- 写入联系人
- 写入外部存储
要请求此类权限,您可以使用以下代码(我MainActivity.java在onCreate方法中使用了它):
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);
}
}
该PERMISSION_REQUEST_READ_PHONE_STATE变量用于确定onRequestPermissionsResult方法中请求的是哪种权限。当然,如果您不需要根据用户是否批准权限来执行任何逻辑,则可以省略此方法:
@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;
}
}
}
应用运行中
这是该应用在模拟器上运行时的效果,测试方法是使用Android Studio 中的Android 设备监视器触发调用:
结论
在这篇文章中,我向大家展示了如何制作一个原生安卓应用,用来屏蔽特定号码的来电。我指出了我遇到的屏蔽问题,目前我仍在寻找解决方案,以隐藏原生来电弹窗——这个弹窗有时会在来电被拒前短暂出现一秒钟。
所以,如果你有什么想法,欢迎提出建议💪
文章来源:https://dev.to/nikola/how-to-make-a-native-android-app-that-can-block-phone-calls--4e15





