Android之拦截手机来电
本项目DEMO:https://github.com/liaozhoubei/EndCallAndClearCacheDemo
在很多手机卫士或者通讯卫士里面都有一项实用的功能,那就是拦截已加入黑名单之中的电话,那么这个功能是如何实现的呢?现在就让我们来看看吧。
我们先看代码,然后再解释其中的意思吧。我们需要监听来电,当有电话打过来的时候马上执行拦截电话的方法,所以要把拦截电话的功能放在Service之中。
完整的拦截电话服务代码如下:
public class EndCallService extends Service {
private TelephonyManager telephonyManager;
private MyPhoneStateListener myPhoneStateListener;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// 监听电话状态
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
myPhoneStateListener = new MyPhoneStateListener();
// 参数1:监听
// 参数2:监听的事件
telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, final String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
// 如果是响铃状态,检测拦截模式是否是电话拦截,是挂断
if (state == TelephonyManager.CALL_STATE_RINGING) {
// 获取拦截模式
// 挂断电话 1.5
endCall();
// 删除通话记录
// 1.获取内容解析者
final ContentResolver resolver = getContentResolver();
// 2.获取内容提供者地址 call_log calls表的地址:calls
// 3.获取执行操作路径
final Uri uri = Uri.parse("content://call_log/calls");
// 4.删除操作
// 通过内容观察者观察内容提供者内容,如果变化,就去执行删除操作
// notifyForDescendents : 匹配规则,true : 精确匹配 false:模糊匹配
resolver.registerContentObserver(uri, true, new ContentObserver(new Handler()) {
// 内容提供者内容变化的时候调用
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// 删除通话记录
resolver.delete(uri, "number=?", new String[] { incomingNumber });
// 注销内容观察者
resolver.unregisterContentObserver(this);
}
});
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);
}
/**
* 挂断电话
*/
public void endCall() {
//通过反射进行实现
try {
//1.通过类加载器加载相应类的class文件
//Class<?> forName = Class.forName("android.os.ServiceManager");
Class<?> loadClass = EndCallService.class.getClassLoader().loadClass("android.os.ServiceManager");
//2.获取类中相应的方法
//name : 方法名
//parameterTypes : 参数类型
Method method = loadClass.getDeclaredMethod("getService", String.class);
//3.执行方法,获取返回值
//receiver : 类的实例
//args : 具体的参数
IBinder invoke = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
//aidl
ITelephony iTelephony = ITelephony.Stub.asInterface(invoke);
//挂断电话
iTelephony.endCall();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
EndCallService的代码已经写好了,然后在mainfest中注册Service,添加拦截电话和删除电话记录的权限:
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
之后我们直接在MainActivity中设置一个按钮点击事件startService就能开启拦截电话的服务了。
解析拦截电话代码
我们首先要知道对于通话来电相关功能是通过TelephonyManager这个API来管理的。
我们点击TelephonyManager,查看它的源码,发现它的方法大部分都被标注为hide,以下是被标注为hide的TelephonyManager的构造方法:
/** @hide */
public TelephonyManager(Context context) {
Context appContext = context.getApplicationContext();
if (appContext != null) {
mContext = appContext;
} else {
mContext = context;
}
mSubscriptionManager = SubscriptionManager.from(mContext);
if (sRegistry == null) {
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
}
}
/** @hide */
private TelephonyManager() {
mContext = null;
}
被标注为hide的代码表示我们无法直接使用,难怪乎必须要用getSystemService(TELEPHONY_SERVICE)来获取TelephonyManager的实例。
继续在TelephonyManager源码中查看,发现一个重要的方法endCall()用于挂断电话,但是这个方法也是被隐藏起来的。
/** @hide */
@SystemApi
public boolean endCall() {
try {
ITelephony telephony = getITelephony();
if (telephony != null)
return telephony.endCall();
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#endCall", e);
}
return false;
}
我们为什么需要endCall()这个方法呢?因为拦截电话的功能实质上是通过挂断电话来实现的,当有电话来电时,系统在第一时间挂断电话,这是属于应用层的拦截电话的方式。
我们找到了挂断电话的方法了,我们惊讶的发现它是通过ITelephony类来实现的,那么这个类又是何方神圣。
查看ITelephony类,它写着以下介绍:
Interface used to interact with the phone. Mostly this is used by the
TelephonyManager class. A few places are still using this directly.
Please clean them up if possible and use TelephonyManager insteadl.
译文:这是用于与手机互动的接口,通常被TelephonyManager类使用,少数地方仍然在直接的使用这个类。如果可以的话请停止使用这个类,并使用TelephonyManager作为代替。
好的,我们终于找到挂断电话的方法了,就是使用ITelephony类,那么我们直接实例化ITelephony类,然后使用endCall()方法吧。
然后我们发现ITelephony是通过以下代码实例化的:
ITelephony telephony = getITelephony();
那么我们直接搜索getITelephony()方法吧,很快我们就搜索到了这个方法:
/**
* @hide
*/
private ITelephony getITelephony() {
return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}
同样这个getITelephony()也是被隐藏的,但是我们只需要获得ITelephony的实例,因此我们直接使用ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE))获得ITelephony
回到我们的EndCallService类,创建我们自己的endCall()方法,然后将ITelephony获得的实例的方法粘贴进去。
但是我们并没有ITelephony的源码,然后发现它是在远程服务中使用的,也就是大名鼎鼎AIDL(进程间通信)。
注:一般使用xxx.Stub的类是AIDL说使用的,又或者是在类前添加I的标明是接口interface类,如ITelephony
我们直接通过互联网获取ITelephony.aidl类,然后在项目的src目录中创建包名com.android.internal.telephony,将ITelephony.aidl粘贴进去。
然而发现ITelephony.aidl还需要导入android.telephony.NeighboringCellInfo这个类,NeighboringCellInfo也是一个AIDL,同样找到它,创建包名android.telephony,将NeighboringCellInfo.aidl粘贴进去。
总算把ITelephony类成功导入项目中,但是ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE))这行代码依旧报错,这是怎么回事呢?
原来我们缺少ServiceManager类,查看ServiceManager类,发现这个类居然被整个被标注为hide!
这时似乎没有任何办法完成任务了!但是不要忘记了编程就是把不可能变成可能!虽然被隐藏了,但是我们仍旧可以通过反射的方式来调用ServiceManager的方法。
//Class<?> forName = Class.forName("android.os.ServiceManager");
Class<?> loadClass = EndCallService.class.getClassLoader().loadClass("android.os.ServiceManager");
Method method = loadClass.getDeclaredMethod("getService", String.class);
IBinder invoke = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
其中Class.forName()和EndCallService.class.getClassLoader().loadClass()方法传入ServiceManager的全类名都可以获得Class文件。
由于ITelephony.Stub.asInterface()要出让如一个IBinder变量,因此我们直接通过反射的方式获取ServiceManager的实例和方法获取IBinder变量。
最后获得了endCall()拦截电话的方法,方法如下:
public void endCall() {
//通过反射进行实现
try {
//Class<?> forName = Class.forName("android.os.ServiceManager");
Class<?> loadClass = EndCallService.class.getClassLoader().loadClass("android.os.ServiceManager");
Method method = loadClass.getDeclaredMethod("getService", String.class);
IBinder invoke = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
ITelephony iTelephony = ITelephony.Stub.asInterface(invoke);
iTelephony.endCall();
} catch (Exception e) {
e.printStackTrace();
}
}
ok,我们把最重要的挂断电话的问题解决了,剩下的监听电话状态就容易办的多了。
监听电话状态
我们通过获取TelephonyManager的实例,然后使用TelephonyManager的listen()方法监听手机的电话状态。listen()方法需要传入两个参数,第一个是参数是监听电话状态,也就是电话处于通话、挂断还是空闲的状态;第二个参数是需要监听电话的事件。
第一个参数的类型是PhoneStateListener类,我们创建了MyPhoneStateListener类继承,重写其中的onCallStateChanged()方法,在有电话拨打过来的时候实现监听,使用endCall()方法挂断电话,并且通过内容观察者删除通话记录。
第二个直接传入PhoneStateListener.LISTEN_CALL_STATE参数,表示我们要监听电话响铃状态。
好了,现在尽情享受不受电话骚扰的日子吧。
本项目DEMO:https://github.com/liaozhoubei/EndCallAndClearCacheDemo