Android之?dāng)r截手機(jī)來(lái)電

本項(xiàng)目DEMO:https://github.com/liaozhoubei/EndCallAndClearCacheDemo

在很多手機(jī)衛(wèi)士或者通訊衛(wèi)士里面都有一項(xiàng)實(shí)用的功能片迅,那就是攔截已加入黑名單之中的電話拷泽,那么這個(gè)功能是如何實(shí)現(xiàn)的呢?現(xiàn)在就讓我們來(lái)看看吧。

我們先看代碼陕贮,然后再解釋其中的意思吧提岔。我們需要監(jiān)聽來(lái)電拘鞋,當(dāng)有電話打過(guò)來(lái)的時(shí)候馬上執(zhí)行攔截電話的方法珊擂,所以要把攔截電話的功能放在Service之中。

完整的攔截電話服務(wù)代碼如下:

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();

    // 監(jiān)聽電話狀態(tài)
    telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
    myPhoneStateListener = new MyPhoneStateListener();
    // 參數(shù)1:監(jiān)聽
    // 參數(shù)2:監(jiān)聽的事件
    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);
        // 如果是響鈴狀態(tài),檢測(cè)攔截模式是否是電話攔截,是掛斷
        if (state == TelephonyManager.CALL_STATE_RINGING) {
            // 獲取攔截模式
            // 掛斷電話 1.5
            endCall();
            // 刪除通話記錄
            // 1.獲取內(nèi)容解析者
            final ContentResolver resolver = getContentResolver();
            // 2.獲取內(nèi)容提供者地址 call_log calls表的地址:calls
            // 3.獲取執(zhí)行操作路徑
            final Uri uri = Uri.parse("content://call_log/calls");
            // 4.刪除操作
            // 通過(guò)內(nèi)容觀察者觀察內(nèi)容提供者內(nèi)容,如果變化,就去執(zhí)行刪除操作
            // notifyForDescendents : 匹配規(guī)則,true : 精確匹配 false:模糊匹配
            resolver.registerContentObserver(uri, true, new ContentObserver(new Handler()) {
                // 內(nèi)容提供者內(nèi)容變化的時(shí)候調(diào)用
                @Override
                public void onChange(boolean selfChange) {
                    super.onChange(selfChange);
                    // 刪除通話記錄
                    resolver.delete(uri, "number=?", new String[] { incomingNumber });
                    // 注銷內(nèi)容觀察者
                    resolver.unregisterContentObserver(this);
                }
            });
        }
    }
}


@Override
public void onDestroy() {
    super.onDestroy();
    telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);
}

/**
 * 掛斷電話
 */
public void endCall() {
    
    //通過(guò)反射進(jìn)行實(shí)現(xiàn)
    try {
        //1.通過(guò)類加載器加載相應(yīng)類的class文件
        //Class<?> forName = Class.forName("android.os.ServiceManager");
        Class<?> loadClass = EndCallService.class.getClassLoader().loadClass("android.os.ServiceManager");
        //2.獲取類中相應(yīng)的方法
        //name : 方法名
        //parameterTypes : 參數(shù)類型
        Method method = loadClass.getDeclaredMethod("getService", String.class);
        //3.執(zhí)行方法,獲取返回值
        //receiver : 類的實(shí)例
        //args : 具體的參數(shù)
        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的代碼已經(jīng)寫好了解虱,然后在mainfest中注冊(cè)Service,添加攔截電話和刪除電話記錄的權(quán)限:

<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中設(shè)置一個(gè)按鈕點(diǎn)擊事件startService就能開啟攔截電話的服務(wù)了漆撞。

解析攔截電話代碼

我們首先要知道對(duì)于通話來(lái)電相關(guān)功能是通過(guò)TelephonyManager這個(gè)API來(lái)管理的殴泰。
我們點(diǎn)擊TelephonyManager,查看它的源碼浮驳,發(fā)現(xiàn)它的方法大部分都被標(biāo)注為hide悍汛,以下是被標(biāo)注為hide的TelephonyManager的構(gòu)造方法:

/** @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;
}

被標(biāo)注為hide的代碼表示我們無(wú)法直接使用,難怪乎必須要用getSystemService(TELEPHONY_SERVICE)來(lái)獲取TelephonyManager的實(shí)例至会。

繼續(xù)在TelephonyManager源碼中查看离咐,發(fā)現(xiàn)一個(gè)重要的方法endCall()用于掛斷電話,但是這個(gè)方法也是被隱藏起來(lái)的奉件。

/** @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;
}

我們?yōu)槭裁葱枰猠ndCall()這個(gè)方法呢宵蛀?因?yàn)閿r截電話的功能實(shí)質(zhì)上是通過(guò)掛斷電話來(lái)實(shí)現(xiàn)的,當(dāng)有電話來(lái)電時(shí)县貌,系統(tǒng)在第一時(shí)間掛斷電話术陶,這是屬于應(yīng)用層的攔截電話的方式。

我們找到了掛斷電話的方法了煤痕,我們驚訝的發(fā)現(xiàn)它是通過(guò)ITelephony類來(lái)實(shí)現(xiàn)的梧宫,那么這個(gè)類又是何方神圣。

查看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.

譯文:這是用于與手機(jī)互動(dòng)的接口塘匣,通常被TelephonyManager類使用,少數(shù)地方仍然在直接的使用這個(gè)類巷帝。如果可以的話請(qǐng)停止使用這個(gè)類忌卤,并使用TelephonyManager作為代替。

好的锅睛,我們終于找到掛斷電話的方法了埠巨,就是使用ITelephony類,那么我們直接實(shí)例化ITelephony類现拒,然后使用endCall()方法吧辣垒。

然后我們發(fā)現(xiàn)ITelephony是通過(guò)以下代碼實(shí)例化的:

ITelephony telephony = getITelephony();

那么我們直接搜索getITelephony()方法吧,很快我們就搜索到了這個(gè)方法:

   /**
     * @hide
     */
private ITelephony getITelephony() {
    return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
}

同樣這個(gè)getITelephony()也是被隱藏的印蔬,但是我們只需要獲得ITelephony的實(shí)例勋桶,因此我們直接使用ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE))獲得ITelephony

回到我們的EndCallService類,創(chuàng)建我們自己的endCall()方法,然后將ITelephony獲得的實(shí)例的方法粘貼進(jìn)去例驹。

但是我們并沒有ITelephony的源碼捐韩,然后發(fā)現(xiàn)它是在遠(yuǎn)程服務(wù)中使用的,也就是大名鼎鼎AIDL(進(jìn)程間通信)鹃锈。

注:一般使用xxx.Stub的類是AIDL說(shuō)使用的荤胁,又或者是在類前添加I的標(biāo)明是接口interface類,如ITelephony

我們直接通過(guò)互聯(lián)網(wǎng)獲取ITelephony.aidl類屎债,然后在項(xiàng)目的src目錄中創(chuàng)建包名com.android.internal.telephony仅政,將ITelephony.aidl粘貼進(jìn)去。

然而發(fā)現(xiàn)ITelephony.aidl還需要導(dǎo)入android.telephony.NeighboringCellInfo這個(gè)類盆驹,NeighboringCellInfo也是一個(gè)AIDL,同樣找到它圆丹,創(chuàng)建包名android.telephony,將NeighboringCellInfo.aidl粘貼進(jìn)去躯喇。

總算把ITelephony類成功導(dǎo)入項(xiàng)目中辫封,但是ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE))這行代碼依舊報(bào)錯(cuò),這是怎么回事呢廉丽?
原來(lái)我們?nèi)鄙賁erviceManager類倦微,查看ServiceManager類,發(fā)現(xiàn)這個(gè)類居然被整個(gè)被標(biāo)注為hide正压!

這時(shí)似乎沒有任何辦法完成任務(wù)了璃诀!但是不要忘記了編程就是把不可能變成可能!雖然被隱藏了蔑匣,但是我們?nèi)耘f可以通過(guò)反射的方式來(lái)調(diào)用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()要出讓如一個(gè)IBinder變量裁良,因此我們直接通過(guò)反射的方式獲取ServiceManager的實(shí)例和方法獲取IBinder變量凿将。

最后獲得了endCall()攔截電話的方法,方法如下:

public void endCall() {
    
    //通過(guò)反射進(jìn)行實(shí)現(xiàn)
    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价脾,我們把最重要的掛斷電話的問(wèn)題解決了牧抵,剩下的監(jiān)聽電話狀態(tài)就容易辦的多了。

監(jiān)聽電話狀態(tài)

我們通過(guò)獲取TelephonyManager的實(shí)例侨把,然后使用TelephonyManager的listen()方法監(jiān)聽手機(jī)的電話狀態(tài)犀变。listen()方法需要傳入兩個(gè)參數(shù),第一個(gè)是參數(shù)是監(jiān)聽電話狀態(tài)秋柄,也就是電話處于通話获枝、掛斷還是空閑的狀態(tài);第二個(gè)參數(shù)是需要監(jiān)聽電話的事件骇笔。

第一個(gè)參數(shù)的類型是PhoneStateListener類省店,我們創(chuàng)建了MyPhoneStateListener類繼承嚣崭,重寫其中的onCallStateChanged()方法,在有電話撥打過(guò)來(lái)的時(shí)候?qū)崿F(xiàn)監(jiān)聽懦傍,使用endCall()方法掛斷電話雹舀,并且通過(guò)內(nèi)容觀察者刪除通話記錄。

第二個(gè)直接傳入PhoneStateListener.LISTEN_CALL_STATE參數(shù)粗俱,表示我們要監(jiān)聽電話響鈴狀態(tài)说榆。

好了,現(xiàn)在盡情享受不受電話騷擾的日子吧寸认。

本項(xiàng)目DEMO:https://github.com/liaozhoubei/EndCallAndClearCacheDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娱俺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子废麻,更是在濱河造成了極大的恐慌,老刑警劉巖模庐,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烛愧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掂碱,警方通過(guò)查閱死者的電腦和手機(jī)怜姿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)疼燥,“玉大人沧卢,你說(shuō)我怎么就攤上這事∽碚撸” “怎么了但狭?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撬即。 經(jīng)常有香客問(wèn)我立磁,道長(zhǎng),這世上最難降的妖魔是什么剥槐? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任唱歧,我火速辦了婚禮,結(jié)果婚禮上粒竖,老公的妹妹穿的比我還像新娘颅崩。我一直安慰自己,他們只是感情好蕊苗,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布沿后。 她就那樣靜靜地躺著,像睡著了一般朽砰。 火紅的嫁衣襯著肌膚如雪得运。 梳的紋絲不亂的頭發(fā)上膝蜈,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音熔掺,去河邊找鬼饱搏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛置逻,可吹牛的內(nèi)容都是我干的推沸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼券坞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鬓催!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起恨锚,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宇驾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后猴伶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體课舍,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年他挎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筝尾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡办桨,死狀恐怖筹淫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呢撞,我是刑警寧澤损姜,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站殊霞,受9級(jí)特大地震影響薛匪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脓鹃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一逸尖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘸右,春花似錦娇跟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至龄章,卻和暖如春吃谣,著一層夾襖步出監(jiān)牢的瞬間乞封,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工岗憋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肃晚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓仔戈,卻偏偏與公主長(zhǎng)得像关串,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子监徘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容