Android项目:手机安全卫士(13)—— 通讯卫士之电话拦截与挂断

Android项目:手机安全卫士(13)—— 通讯卫士之电话拦截与挂断

1 介绍

上一节我们讲了黑名单数据的存储等 CRUD 操作,今天,就到了它们发挥作用的时候了,通讯卫士功法终于要练成了。我们实现了手机的短信、电话拦截功能。

关于项目相关文章,请访问:

项目源码地址(实时更新):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 哦:

AIDL 文件存放目录

接下的工作就是…什么,你觉得这就结束了吗,不可能,哪有这么简单。以上代码不仅需要权限:<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 总结

今天的内容我认为全是干货啊,特别是电话拦截挂断的功能,以后可以直接拿来用了,省去了不少事。

关于项目相关文章,请访问:

项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xwdoor/article/details/50915457
文章标签: android 手机安全
个人分类: Android
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭