要说拦截Android系统来电,就不得不说起在低版本的时候Android提供给开发者使用的一个方法:endCall(),但由于谷歌后来考虑到对于一部手机来说,最重要的功能就是打电话了,如果这个功能随随便便就被人屏蔽了,安全性太差,所以在高版本的Android将这个方法屏蔽了,不再在TelephoneManager中暴露这个方法。
那么我们下面的目标就是要想办法调用到这个方法,当然首先我们还是需要实现一个广播接收者,来接收电话状态改变的广播,这里使用在服务中动态注册广播接收者的方法来实现,主要好处在于便于控制广播接收者的生命周期,同时也能在权限值相同的情况下比静态注册优先级更高
在服务的onCreate()方法中注册广播接收者:
private static final String PHONE = "PHONE";
private static final String BOTH = "BOTH";
private static final String SMS = "SMS";
private TelephonyManager tm;
private BlacklistDao dao;
private inCommingCallReceiver callReceiver;
private PhoneStateListener listener;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
dao = new BlacklistDao(this, 1);
callReceiver = new inCommingCallReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.PHONE_STATE");
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
filter.setPriority(Integer.MAX_VALUE);
listener = new PhoneStateListener() {
private BlacklistItem blacklistItem;
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:// 如果是来电的时候
blacklistItem = dao.queryItem(incomingNumber);
if (blacklistItem != null) {
String type = blacklistItem.getType();
if ((BOTH.equals(type) || PHONE.equals(type))) {
System.out.println("挂断电话");
<strong>hangUpCallFromBlacklist(incomingNumber);//挂断电话的方法
}
}
break;
default:
break;
}
}
};
registerReceiver(callReceiver, filter);// 注册广播接收者
}
在服务的onDestroy()方法中取消注册广播接收者:
@Override
public void onDestroy() {
super.onDestroy();
System.out.println("关闭黑名单服务");
unregisterReceiver(callReceiver);
// 取消监听
tm.listen(listener, PhoneStateListener.LISTEN_NONE);
// listener = null;
}
内部类广播接收者,用于接收电话状态改变的广播:
/**
* 监听来电
*
* @author Alex
*
*/
private class inCommingCallReceiver extends BroadcastReceiver {
private BlacklistItem blacklistItem;
@Override
public void onReceive(Context context, Intent intent) {
if ("android.intent.action.PHONE_STATE".equals(intent
.getAction())) {
// 如果收到的是电话状态的变化
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
}
下面我们重点来看hangUpCallFromBlacklist(incomingNumber);这个实现挂断来电的方法,其中incomingNumber是来电的电话号码。
按照我的惯例,还是从安卓系统的源码入手,由于endCall方法原来是在TelephoneManager中的,所以我们不妨从TelephoneManager的实例化方法getSystemService入手:
我们可以发现这个方法是定义在ContextWrapper中的,而ContextWrapper又是继承自Context,我们不妨进入到Context的源码中去一探究竟:
在Context中,我们只找到了一个getSystemService的抽象方法,那么如何去找实现方法呢,在java中,抽象类的实现类一般名字都是在抽象类名后面加上一个"Impl",于是我们去搜索源码中的ContextImpl.java,找到之后打开发现:
我们发现getSystemService实际上返回了一个ServiceFetcher对象的一个getService方法的结果,我们来看看ServiceFetcher的getService方法:
在这里我们可以看到,在定义了一个static块中,注册了很多不同的service服务,而这些gerService方法都是由ServiceManager来调用的,返回值是一个IBinder对象。接下来我们可以在文件的导入包的部分找到ServiceManager的位置是在android/os下的:
由于getService返回的是一个IBinder对象,我们只要找到这个getService方法的实现,就可以传入TELEPONY_SERVICE从而拿到真正的TelephonyManager所代理的那个远程服务绑定对象,从而调用隐藏在其中的endCall方法。
幸运的是,在ServiceMnager.java中,我们找到了getService方法的实现:
我们可以发现,ServiceManager这个类也是一个隐藏类,我们无法在我们的代码中直接拿到这个类来调用其中的getService方法来获取IBinder对象,那么我们要如何做呢?
这里就只有使用反射的方法来处理:
Class clazz = CallSmsSafeService.class.getClassLoader().loadClass(
"android.os.ServiceManager");
Method method = clazz.getMethod("getService", String.class);
IBinder binder = (IBinder) method.invoke(null, TELEPHONY_SERVICE);
通过上面的反射做法,我们拿到了对应于TelephonyManager的IBinder对象,下面我们需要做的利用aidl来调用远程方法,既然是使用的TelephonyManager的IBinder对象,我们再进入到TelephonyManager的源码中去看看:
我们发现,在TelephonyManager中,类似于getCallState()这类的方法基本都返回的是getITelephony()的返回值调用的方法,那么这个getITelephy()是什么呢:
我们发现,返回的实际上是一个ITelephony对象,而且是以一种调用远程服务方法的形式返回的;
我们在文件的头部找到ITelephony的位置:
打开上面的目录,我们发现ITelephony是一个aidl文件,进入其中,我们可以找到endCall方法:
由于在ITelephony.aidl的头部有如下信息:
我们想要通过aidl来调用远程服务ITelephony的方法endCall(),我们需要将Telehpony.aidl拷贝到我们工程中新建的com.android.internal.telephony包中,同时将android.telephony.NeighboringCellInfo.aidl文件拷贝到工程中新建的android.telephony包中,这样在gen目中下就会自动生成一个对应的ITelephony.java文件。至此,我们就可以使用下面的语句来调用远程服务的endCall方法:
ITelephony.Stub.asInterface(binder).endCall();
最后,不要忘记在清单文件中加入对应的权限:
<!-- 授予该应用控制通话的权限 -->
<uses-permission android:name="android.permission.CALL_PHONE">
<!-- 授予该应用读取通话状态的权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE">