本項(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