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,添加拦截电话和删除电话记录的权限:





之后我们直接在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

你可能感兴趣的:(Android之拦截手机来电)