之前用的手机上没有黑名单功能,下载第三方的软件又觉得不安全,所以自己写了一个简单版本的凑合用,在这里记录一下。
因为之前有做过短信的拦截相关功能,但是电话拦截接触也不是很多,所以并没有做过详细的测试(自己在两款手机上跑过,都是ok的),在做这个功能的时候也参考过几篇博客,但是具体地址没记住。
电话拦截的实现其实就是由电话的监听和电话的挂断两个部分组成,其中电话监听就是通过注册广播来实现监听的,挂断电话稍微复杂一些,是用到了Android的跨进程调用的AIDL。
1、首先看一下电话监听,我们需要监听android.intent.action.PHONE_STATE这个广播和android.intent.action.NEW_OUTGOING_CALL这个广播,其中一个是来电状态,一个是拨出电话的监听。我们可以在AndroidManifest中静态注册来电广播的接收器,如下:
然后定义一个TelReceive类,继承自BroadcastReceiver,然后在receiver中收到来电或者拨打电话的请求后,处理对应的状态。
private static final String PHONE_INCOMING_KEY = "incoming_number";
@Override
public void onReceive(Context context, Intent intent)
{
//如果是拨打电话
if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL))
{
incomingFlag = false;
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Tool.BfLog("打了电话:"+ phoneNumber);
}
else
{
//如果是来电
TelephonyManager tm =
(TelephonyManager)context.getSystemService(Service.TELEPHONY_SERVICE);
switch (tm.getCallState())
{
case TelephonyManager.CALL_STATE_RINGING:
//标识当前是来电
incomingFlag = true;
String incoming_number = intent.getStringExtra(PHONE_INCOMING_KEY);
Tool.BfLog("打进来了电话:"+ incoming_number);
isInBlackList(context, incoming_number);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if(incomingFlag)
{
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if(incomingFlag)
{
}
break;
}
}
}
2、电话挂断
在上面的代码中我们已经可以监听到来电的电话号码,剩下的事情只要判断是否在黑名单,如果是的话挂断电话就可以了。判断是否在黑名单是比较简单的,我们可以写一个界面,让用户输入黑名单的号码,然后把黑名单信息保存起来,等到来电话的时候就读取出来进行对比,这个地方我们不做过多介绍。挂断电话是比较麻烦的,因为Android的API中是没有挂断电话的功能的,那么我们该如何实现呢?
首先我们需要知道Android源码中如何挂断电话的,那就是用ITelephony的endCall方法,那我们如何得到ITelephony这个对象呢,这个地方就需要用到Android的AIDL,AIDL(Android Interface Definition Language, Android接口定义语言)简单来说是可以实现进程间通信的技术,这里我们需要两个Android系统源码中的两个文件,一个是com.android.internal.telephony包下的ITelephony.aidl一个是android.telephony包下的NeighboringCellInfo.aidl,
ITelephony.aidl文件内容
package com.android.internal.telephony;
interface ITelephony
{
boolean endCall();
void answerRingingCall();
}
NeighboringCellInfo.aidl文件内容
package android.telephony;
parcelable NeighboringCellInfo;
/**
* 判断是否是在黑名单中的号码
*
* @param context
* 上下文
* @param num 来电的号码
*/
public void isInBlackList(Context context, String num)
{
String telstemp[] = DataBean.getInstance().getBlackList();
for(String temp:telstemp)
{
if(temp.contains(num))
{
// 拦截来电
stop(context, num);
// 记录日志
Time time = new Time("GMT+8");
time.setToNow();
String times = time.year+"."+time.month+"."+time.monthDay+" "+time.hour+":"
+time.minute+":"+time.second;
DataBean.getInstance().addTelItem(num + "," + times);
MainListener.getInstance().refreshFragmentList(FragmentTel.FLAGS);
}
}
}
/**
* 结束通话
*
* @param context
* 上下文环境
* @param incoming_number
* 打来的电话号码
*/
public void stop(Context context ,String incoming_number)
{
AudioManager mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
//静音处理
mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
//获取电话接口
ITelephony iTelephony = getITelephony(context);
try
{
iTelephony.endCall();//结束电话
}
catch (RemoteException e)
{
e.printStackTrace();
}
//再恢复正常铃声
mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
/**
* 获取系统的电话实例
*
* @param context
* 上下文
* @return 电话实例
*/
private static ITelephony getITelephony(Context context)
{
ITelephony iTelephony = null;
TelephonyManager telephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
try
{
Method getITelephonyMethod = TelephonyManager.class.getDeclaredMethod("getITelephony", (Class[]) null);
getITelephonyMethod.setAccessible(true);
iTelephony = (ITelephony) getITelephonyMethod.invoke(telephonyMgr, (Object[]) null);
}
catch (Exception e)
{
e.printStackTrace();
}
return iTelephony;
}
到目前为止我们监听到了来电并且把来电挂断了,实现了我们的电话拦截功能,同时也保存了日志信息。下面我们介绍如何拦截短信消息。
短信拦截其实是比较简单的一种,网上有很多相关介绍,这里主要介绍一下在写的过程中遇到的一些问题。
首先短信拦截的实现原理也是监听短信的广播,然后判断短信的号码,判断是否是在黑名单,如果是的话就用abortBroadcast方法结束广播的传递就可以了。其中在做的过程中最主要的问题不是收到监听,而且最先收到监听,因为短信的广播是有序广播,那么谁最先收到广播,谁就可以有权利结束广播的传递,所以我们实际上在做的时候是要想办法把我们接收广播的权限提到最高,这里主要是两个方法,1是设置权限值最大,2是注册方式设置为动态注册的。
设置权限最大,其实就是把注册广播时的优先级设置最大,其中Android系统api中说明最大权限是1000,而在实际上接收的是一个int值,而且系统没有判断值的上线,所以我们可以设置int的最大值,这个权限是最高的。
那么如果都是最大权限了,谁的优先级高呢,那么就是第二点,动态注册监听,因为在源码中动态注册的广播是在静态广播之前放入监听列表中的,所以我们这里用动态注册来设置监听。
那如果大家也都用动态注册的方式了,谁先收到呢,这个地方好像又跟应用的包名有关系,具体我也没有搞太明白,大概意思是安装时间越早,优先级越高,包名在系统中的别名顺序越靠前,优先级越高(不是我们写的包名的字母排序)。
那么我们来看一下具体实现,首先是建一个service,然后保证这个service一直在后台运行(可以用守护进程,监听开机广播等等方式保证一直存在),然后在service的onStartCommand和onDestroy方法中分别注册广播和取消注册。
private BroadcastReceiver smsReceive = new SmsReceive();
@Override
public IBinder onBind(Intent arg0)
{
return null;
}
@Override
public void onCreate()
{
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// 注册短信监听广播
registerSmsReceiver();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy()
{
// 解绑短信监听广播
unRegisterSmsReceiver();
super.onDestroy();
}
/**
* 注册短信监听广播
*/
private void registerSmsReceiver()
{
IntentFilter filter = new IntentFilter();
filter.addAction(SmsReceive.ACTION_SMS_RECEIVE);
filter.setPriority(Integer.MAX_VALUE);
registerReceiver(smsReceive, filter);
}
/**
* 解绑短信监听
*/
private void unRegisterSmsReceiver()
{
unregisterReceiver(smsReceive);
}
/**
* 短信广播的监听
*
* @author jeden
*
*/
public class SmsReceive extends BroadcastReceiver
{
public static final String ACTION_SMS_RECEIVE = "android.provider.Telephony.SMS_RECEIVED";
public void onReceive(Context context, Intent intent)
{
String actionName = intent.getAction();
if (actionName.equals(ACTION_SMS_RECEIVE))
{
StringBuffer SMSAddress = new StringBuffer();
StringBuffer SMSContent = new StringBuffer();
Bundle bundle = intent.getExtras();
if (bundle != null)
{
Object[] myOBJpdus = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[myOBJpdus.length];
for (int i = 0; i < myOBJpdus.length; i++)
{
messages[i] = SmsMessage
.createFromPdu((byte[]) myOBJpdus[i]);
}
for (SmsMessage message : messages)
{
SMSAddress.append(message
.getDisplayOriginatingAddress());
SMSContent.append(message.getDisplayMessageBody());
Tool.BfLog( "收到的短信::"+"来信号码:" + SMSAddress + "\n短信内容:"
+ SMSContent);
String[] telstemp = DataBean.getInstance().getBlackList();
for(String temp:telstemp)
{
if(temp.contains(SMSAddress))
{
// 添加拦截信息
DataBean.getInstance().addMsgItem(SMSAddress + "," + SMSContent);
MainListener.getInstance().refreshFragmentList(FragmentMsg.FLAGS);
abortBroadcast();
}
}
}
}
}
}
}
其中添加拦截信息和刷新列表是自己做了一个页面用来展示拦截到的信息的。
ok,这样电话拦截和短信拦截功能就实现了,功能还是比较简单的!