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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容