Android项目:手机安全卫士(13)—— 通讯卫士之电话拦截与挂断
1 介绍
上一节我们讲了黑名单数据的存储等 CRUD 操作,今天,就到了它们发挥作用的时候了,通讯卫士功法终于要练成了。我们实现了手机的短信、电话拦截功能。
关于项目相关文章,请访问:
- Android 项目:手机安全卫士(7)—— 手机防盗功能
- Android 项目:手机安全卫士(8)—— 管理员权限
- Android 项目:手机安全卫士(9)—— 手机号码归属地查询
- Android 项目:手机安全卫士(10)—— 电话归属地显示
- Android 项目:手机安全卫士(11)—— 归属地提示框拖拽效果
- Android 项目:手机安全卫士(12)—— 通讯卫士之电话、短信黑名单设置与拦截
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe
2 添加拦截服务的设置菜单
在设置界面,添加一个 Item,用于设置是否开启黑名单拦截服务,效果如下:
聪明如你,不知道有没有发现,我们的设置界面已经越来越完善了,Item 也越来越多咯,哈哈。这里给出它的后台代码:
/** 初始化黑名单设置 */
private void initBlackNumber() {
final SettingItemView sivBlackNumber = (SettingItemView) findViewById(R.id.siv_black_number);
// 根据服务是否运行来更新checkbox
boolean serviceRunning = ServiceStatusUtils.isServiceRunning(this,
"net.xwdoor.mobilesafe.service.BlackNumberService");
sivBlackNumber.setChecked(serviceRunning);
sivBlackNumber.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sivBlackNumber.setChecked(!sivBlackNumber.isChecked());
Intent service = new Intent(getApplicationContext(),
BlackNumberService.class);
if(sivBlackNumber.isChecked()){
startService(service);
}else {
stopService(service);
}
}
});
}
每次进入设置界面都会初始化,判断拦截服务是否开启,从而设置选中状态。然后根据用户的设置情况,实时开启或停止服务。当然,再次之前,咱们需要创建一个 Service:BlackNumberService,这个工作比较简单,使用 Android Studio 的“自动化服务”开始创建:
/**
* 黑名单服务
*
* Created by XWdoor on 2016/3/17 017 13:41.
* 博客:http://blog.csdn.net/xwdoor
*/
public class BlackNumberService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//拦截短信
// 拦截电话
}
@Override
public void onDestroy() {
super.onDestroy();
//停止监听短信
// 停止监听来电
}
}
BlackNumberService 主要用于黑名单的电话与短信拦截,下面就来一一讲解。
3 黑名单短信拦截
先来做短信拦截的服务,这个比较简单,我记得,前面实现手机防盗功能的时候,我们也用了短信监听与拦截功能,这次的代码差不多,我们只要稍作修改,符合业务需求即可。创建广播 BlackNumberSmsReceiver:
/**
* 监听短信广播:黑名单监听
*
* Created by XWdoor on 2016/3/17 017 14:03.
* 博客:http://blog.csdn.net/xwdoor
*/
public class BlackNumberSmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Object[] pdus = (Object[]) intent.getExtras().get("pdus");
for (Object pdu : pdus) {
SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu);
String address = message.getOriginatingAddress();
String msg = message.getMessageBody();
BlackNumberDao mNumberDao = BlackNumberDao.getInstance(context);
boolean exist = mNumberDao.find(address);
if (exist) {// 在黑名单中
// 1, 2, 3
int mode = mNumberDao.findMode(address);// 获取拦截模式
if (mode > 1) {// 2, 3才拦截
abortBroadcast();
}
}
mNumberDao = null;
}
}
}
哈哈,也就几行代码嘛,这里我们只做了最简单的拦截,就是根据电话号码进行拦截:若短信号码存在黑名单中,则拦截短信。还有一个问题,就是 abortBroadcast() 方法可能在高版本中就不够用了,还需要查询存储短信的数据库,然后删除数据库中的数据。
有了 BlackNumberSmsReceiver 广播,就可以在服务 BlackNumberService 中实现骚扰短信的拦截功能,在 onCreate() 方法中添加以下代码:
//拦截短信
IntentFilter filter = new IntentFilter();
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
filter.setPriority(Integer.MAX_VALUE);
mSmsReceiver = new BlackNumberSmsReceiver();
// 同等条件下, 动态注册的广播比静态注册的更先获取广播内容
registerReceiver(mSmsReceiver,filter);
服务开启的时候开启短信拦截服务,同样的,在服务停止的时候,需要关闭短信拦截服务,在 onDestroy() 方法中添加以下代码:
//停止监听短信
unregisterReceiver(mSmsReceiver);
mSmsReceiver = null;
4 黑名单电话拦截与挂断
接下来就是本文的重点了,实现了短信拦截,就轮到电话拦截了。首先在 BlackNumberService 服务中实现电话监听的功能,在 onCreate() 方法中添加以下代码:
// 拦截电话
mTM = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
mPhoneListener = new BlackNumberPhoneListener(this);
mTM.listen(mPhoneListener, PhoneStateListener.LISTEN_CALL_STATE);
同时,在 onDestroy() 方法中添加以下代码,用于停止监听电话:
// 停止监听来电
mTM.listen(mPhoneListener,PhoneStateListener.LISTEN_NONE);
监听电话需要传入一个 PhoneStateListener 对象,这里我们创建一个类 BlackNumberPhoneListener,继承自 PhoneStateListener,同时复写它的 onCallStateChanged() 方法,代码如下:
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state){
case TelephonyManager.CALL_STATE_RINGING://电话铃响
boolean exist = mNumberDao.find(incomingNumber);
if(exist){//黑名单中有该号码
int mode = mNumberDao.findMode(incomingNumber);
if (mode == 1 || mode == 3) {
//拦截该号码:挂断电话
endCall();
}
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK://电话摘机
break;
case TelephonyManager.CALL_STATE_IDLE://电话空闲
break;
}
}
当监听到需要拦截的电话时,就需要挂断电话,这个功能实现起来比较复杂,先说思路:在 android 1.X 版本中的 TelephonyManager 有一个 api 接口:endCall(),调用它就可以直接挂断电话,但是马上,google 在 2.X 以上的版本中隐藏了这个 api,因为这个操作太敏感了,这里需要注意,是隐藏 api,而不是删除哦,我们的办法就是想方设法的调用 endCall() 这个方法。至于怎么调用,你先看看代码:
/** 挂断电话 需要权限:android.permission.CALL_PHONE */
private void endCall() {
try {
// TelephonyManager.endCall();
// IBinder b = ServiceManager.getService(ALARM_SERVICE);
// IAlarmManager service = IAlarmManager.Stub.asInterface(b);
// ServiceManager 被隐藏了,需要通过反射来调用
// IBinder b = ServiceManager.getService(TELEPHONY_SERVICE);
Class<?> aClass = Class.forName("android.os.ServiceManager");//通过反射找到ServiceManager
Method method = aClass.getMethod("getService", String.class);//找到ServiceManager的静态方法getService
IBinder b = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);//调用getService()方法,得到IBinder对象
ITelephony service = ITelephony.Stub.asInterface(b);//得到TelephonyManager接口
service.endCall();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
每句代码我都写了注释,以免以后查阅。通过查看 android 的源代码得知,每次我们调用 getSystemService() 方法时,系统都是通过调用 ServiceManager 类的静态方法 getService() 得到 IBinder 对象,从而得到相应的服务。所以,我们就按照这个思路走,首先,我们通过 java 的反射机制找到 ServiceManager 对象;然后通过 getMethod() 方法找到 getService() 方法;然后调用该方法得到对应的 IBinder 对象,从而得到 TelephonyManager 中的 ITelephony 对象;最后就是调用 endCall() 方法了。
需要声明一下,关于 IBinder、ITelephony、Stub 什么的,我目前也不太懂,相信随着学习的加深,以后肯定会接触的。还有一点就是,使用 IBinder、ITelephony 等接口类需要用到两个 AIDL 文件,这两个文件我的项目中有,需要说明的是,它们的存放位置有点技巧,需要存放在 aidl 目录中,且该目录是与 java 同一级别的目录,说的有点抽象,看看下面的图片就一目了然了,注意,创建了很多 package 哦:
接下的工作就是…什么,你觉得这就结束了吗,不可能,哪有这么简单。以上代码不仅需要权限:<uses-permission android:name="android.permission.CALL_PHONE" />
,测试后还发现,虽然电话成功拦截了,但是打开通话记录,还能看到被拦截的电话的记录,所以自己挖的坑,还得自己填,我们需要删除通话记录。
5 删除通话记录
调用 endCall() 方法后,传入一个观察者:BlackNumberLogObserver,继承自 ContentObserver,注册观察者的代码如下:
// 注册内容观察者,观察通话记录表的变化
mObserver = new BlackNumberLogObserver(new Handler(), incomingNumber);
mContext.getContentResolver().registerContentObserver(
Uri.parse("content://call_log/calls"), true, mObserver);
类 BlackNumberLogObserver 需要重写 onChange() 方法,用于删除通讯录,以及注销观察者:
// 表的数据发生变化会回调此方法
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
deleteCallLog(number);
// 注销观察者
mContext.getContentResolver().unregisterContentObserver(mObserver);
}
/**
* 删除通话记录
*
* 需要权限:
* <uses-permission android:name="android.permission.READ_CALL_LOG" />
* <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
*
* @param number 电话
*/
private void deleteCallLog(String number) {
// 和联系人是一个数据库
mContext.getContentResolver().delete(Uri.parse("content://call_log/calls"), "number = ?", new String[]{number});
}
到此,我们的电话拦截才告一段落,可以轻松一下了。
6 总结
今天的内容我认为全是干货啊,特别是电话拦截挂断的功能,以后可以直接拿来用了,省去了不少事。
关于项目相关文章,请访问:
- Android 项目:手机安全卫士(7)—— 手机防盗功能
- Android 项目:手机安全卫士(8)—— 管理员权限
- Android 项目:手机安全卫士(9)—— 手机号码归属地查询
- Android 项目:手机安全卫士(10)—— 电话归属地显示
- Android 项目:手机安全卫士(11)—— 归属地提示框拖拽效果
- Android 项目:手机安全卫士(12)—— 通讯卫士之电话、短信黑名单设置与拦截
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe