功能描述:
总的来说这是一个防骚扰的应用,设置黑名单,白名单,通话录音名单。添加到黑名单的联系人或号码将被拒绝来电或短信;添加到白名单的联系人或号码将通过来电或短信(除白名单以外的号码将被拒绝来电或短信),因此逻辑上黑名单和白名单是不能同时开启的;添加到通话录音列表的联系人或号码,连接通话时将会开启录音,挂断时完成录音。
先上图,接着分析实现这几个部分的关键技术点,最后附上安装程序apk和工程源码.
要重点具备的知识:
电话拦截部分:
电话是手机最基本的服务,自然在系统服务中可以获取:TelephonyManager manager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
我们要对电话进行拦截,自然首先想到就就是我们怎么获知电话何时是响起、何时接通、挂断?我刚开始做的时候首先想到的就是电话响起、接通和挂断的时候会不会主动发送一个广播?没错,来电的时候会发出一个action为android.intent.action.PHONE_STATE所以我们写一广播接收器:
public class PhoneReceiver extendsBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
// 电话服务管理
TelephonyManager manager =(TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
//处理拦截逻辑
}
}
但是到这里有碰到疑问了,我知道此时是来电了,但是我怎么知道用户进一步的动作,响铃?接听?挂断?。查阅了些资料,原来TelephonyManager可以绑定一个监听器监听电话状态: PhoneStateListener
private PhoneStateListener listener = new PhoneStateListener() {
/*
*@see TelephonyManager#CALL_STATE_IDLE 值为0
*
*@see TelephonyManager#CALL_STATE_RINGING 值为1
*
*@see TelephonyManager#CALL_STATE_OFFHOOK 值为2
*/
@Override
public void onCallStateChanged(int state,String incomingNumber) {
super.onCallStateChanged(state,incomingNumber);
switch (state) {
caseTelephonyManager.CALL_STATE_IDLE: //挂断时
break;
caseTelephonyManager.CALL_STATE_RINGING: // 响铃时
// 可以在这里加上电话拦截逻辑
break;
caseTelephonyManager.CALL_STATE_OFFHOOK: // 接起电话 // 可以这里加上电话录音的处理逻辑
break;
default:
break;
}
}
[img][/img]
};
//注册监听器
manager.listen(listener,PhoneStateListener.LISTEN_CALL_STATE);
自此,我们可以成功的监控电话的状态了。这个是时候估计你也想做个小小的测试了吧,别忘了,添加权限:
<!-- 读取电话状态权限-->
<uses-permission ndroid:name="android.permission.READ_PHONE_STATE"/>
下面说说电话拦截过滤逻辑:
拦截过滤的原理很简单,相信大家都想得到,就是检查来电号码和我们预设的黑名单或者白名单列表里的号码依依做对比,如果需要拦截强制挂断,否则放过。Ok,现在问题的关键就是如何强制挂断,你可能首先找TelephonyManager是否提供了相应的方法,我开始也是这样认为的,比较遗憾,我又错了。Internet是个不错的东西,最后到网上看到有人用了这个类:ITelephony。
这个类在android私有api中,所以你在官方文档上是查不到的,下载android的源码可以看到位于frameworks/base/telephony/java/com/android/internal/telephony 目录下,包名为:com.android.internal.telephony
其实这里用的了AIDL(IPC跨进程通讯),关于AIDL不清楚的,可以google一下了解下概念,然后可以到这里参考下一个小例子:
http://mgssnake.iteye.com/blog/655866
运行调试理解下这个小例子之后估计你读AIDL也有个大概的认识。我对AIDL表面化而粗陋的理解就是提供服务的一段注册一个服务这个服务必实现或者能提供一个实现了AIDL接口中内部抽象类Stub类的子类(提供服务端必定有一个Sub的子类),在调用服务的一端通过AIDL的路径查询获取服务端的这个Stub的子类的实例binder,在通过Sub的静态方法asInterface(binder)获取服务代理,从而可以随心所以操作远端提供的服务。
关于ITelephony,可以参考下这里有篇文章:
http://hi.baidu.com/wentaokou/blog/item/3e4fe9d0e5fdc02e9a5027f0.html
ok,大概了解了这些现在回到我们的项目中:
在工程目录下新建一个包com.android.internal.telephony 将android源码中的ITelephony.aidl考进来,这是好像编译不过,原因是在这个文件中引用了另一个aidl文件:NeighboringCellInfo.aidl这个文件位于android源码frameworks/base/telephony/java/android/telephony目录中,包名为:android.telephony,所以我们再建一个包:android.telephony将NeighboringCellInfo.aidl拷贝进来即可。如果你还在纠结于怎么没有android源码,文章的最后会贴上本项目源码或者直接q我向我要。
下面重点看看如何获取ITelephony的服务。
//通过反射取得android.os.ServiceManager的getService方法对象
Method method =Class.forName("android.os.ServiceManager")
.getMethod("getService", String.class);
//执行方法传入参数Service.TELEPHONY_SERVICE,此处返回的应当是ITelephony.Stub的子类对象
IBinder binder =(IBinder) method.invoke(null,
new Object[] {Service.TELEPHONY_SERVICE });
//转化成一个代理对象
ITelephonytelephony = ITelephony.Stub.asInterface(binder);
//调用挂断电话的方法
telephony.endCall();
短信拦截部分:
同样的原理当系统检查的有短信来时首先会像系统发送广播:android.provider.Telephony.SMS_RE,所以我们写一广播接收器SMSReciever即可。
public class SMSRecieverextends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//接受sm传来的数据
Bundle bundle = intent.getExtras();
if(bundle!=null){
//通过pdus可以获得收到的所有信息
Object[] objects = (Object[]) bundle.get("pdus");
//构建对象对象数组
SmsMessage[] messages = new SmsMessage[objects.length];
for (int i = 0; i < messages.length; i++) {
messages[i]= SmsMessage.createFromPdu((byte[])objects[i]);
}
for (SmsMessagesmsMessage : messages) {
String number = smsMessage.getOriginatingAddress()
//拦截过滤
Boolean flag = check(number);
if(flag){//如果拦截,调用abortBroadcast()方法中断广播
abortBroadcast();
}
}
}
}
}
这部分比较简单但在注册广播接收器的时候要注意一点:
IntentFilter filter = new IntentFilter();
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
filter.setPriority(2147483647);//优先级尽量开大下不然拦截不到
registerReceiver(new SMSReciever(), filter);
你应该想的到,一个广播的发送,接收广播的广播接收器应该不只一个,短信广播更不列外,手机短信来的时候一定有广播负责通知并启动短信铃声、一定有广播负责将短信写入数据库等,而我们的目的就是要在这些动作发生之前拦截掉,那么我们怎么就能保证我们写的广播接收器先于系统内置的广播接收器接收到广播呢。
如果多个广播接收器中含有相同的action,那么他们接收到广播的顺序是根据他们的Priority属性来判断的,Priority值越大优先级也高,越先接受到广播,在某个广播接收器中调用了abortBroadcast()方法终端广播,优先级比他低的广播接收器就接受不到该广播了。
所以在我们的拦截短信的广播接收器中尽量将优先级设置大些,保险起见你可以设置为整型的最大值。看下图:
电话录音部分:
逻辑很简单,不解释,上关键代码:
// 进行录音
private void recordCalling() {
try {
Log.v("TAG", "recordCalling");
recorder = new MediaRecorder();
// 读麦克风的声音
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 输出格式.3gp
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
// 编码方式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// 存放的位置是放在sdcard目录下,当然你可以自己指定一个更合理的目录
recorder.setOutputFile(Environment.getExternalStorageDirectory()
.getAbsolutePath()
+"/" + System.currentTimeMillis() + ".3gp"); recorder.prepare();
recorder.start();
recording = true;
} catch (Exception e) {
e.printStackTrace();
}
}
// 停止录音
private void stopRecord() {
Log.v("TAG", "stopRecord");
if (recording) {
recorder.stop();
recorder.release();
recording = false;
}
}
关于MediaRecorder 最详细最权威的介绍见google官方文档:http://developer.android.com/reference/android/media/MediaRecorder.html
最后附上整个工程的源码和apk安装程序