Android之拦截手机来电

作者: 宝塔山上的猫 | 来源:发表于2016-07-19 15:56 被阅读3320次

    本项目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

    相关文章

      网友评论

        本文标题:Android之拦截手机来电

        本文链接:https://www.haomeiwen.com/subject/aawmjttx.html