1. 程式人生 > >Android 電話和簡訊攔截

Android 電話和簡訊攔截

之前用的手機上沒有黑名單功能,下載第三方的軟體又覺得不安全,所以自己寫了一個簡單版本的湊合用,在這裡記錄一下。

因為之前有做過簡訊的攔截相關功能,但是電話攔截接觸也不是很多,所以並沒有做過詳細的測試(自己在兩款手機上跑過,都是ok的),在做這個功能的時候也參考過幾篇部落格,但是具體地址沒記住。

一、電話攔截

電話攔截的實現其實就是由電話的監聽和電話的結束通話兩個部分組成,其中電話監聽就是通過註冊廣播來實現監聽的,結束通話電話稍微複雜一些,是用到了Android的跨程序呼叫的AIDL。

1、首先看一下電話監聽,我們需要監聽android.intent.action.PHONE_STATE這個廣播和android.intent.action.NEW_OUTGOING_CALL這個廣播,其中一個是來電狀態,一個是撥出電話的監聽。我們可以在AndroidManifest中靜態註冊來電廣播的接收器,如下:

<receiver android:name=".main.TelReceive">
	<intent-filter>
		<action android:name="android.intent.action.PHONE_STATE"/>           
	        <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
	</intent-filter>
</receiver>

然後定義一個TelReceive類,繼承自BroadcastReceiver,然後在receiver中收到來電或者撥打電話的請求後,處理對應的狀態。

    private static final String PHONE_INCOMING_KEY = "incoming_number";

    @Override
    public void onReceive(Context context, Intent intent) 
    {    
        //如果是撥打電話
        if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL))
        {   
            incomingFlag = false;
            String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); 
            Tool.BfLog("打了電話:"+ phoneNumber);
    	}
        else
    	{                        
            //如果是來電
            TelephonyManager tm = 
                (TelephonyManager)context.getSystemService(Service.TELEPHONY_SERVICE);                        
            
            switch (tm.getCallState()) 
            {
	            case TelephonyManager.CALL_STATE_RINGING:
	            	
	            //標識當前是來電
	            incomingFlag = true;
                    String incoming_number = intent.getStringExtra(PHONE_INCOMING_KEY);
                	
                    Tool.BfLog("打進來了電話:"+ incoming_number);
                    isInBlackList(context, incoming_number);
                    break;
	            case TelephonyManager.CALL_STATE_OFFHOOK:                                
                    if(incomingFlag)
                    {
                    }
                    break;
	            
	            case TelephonyManager.CALL_STATE_IDLE:                                
                    if(incomingFlag)
                    {       
                    }
                    break;
            } 
        }
    }

解釋一下上面的程式碼,其中當廣播是Intent.ACTION_NEW_OUTGOING_CALL的時候,表示要撥打電話了,這裡通過intent獲取到撥打電話的號碼,因為這裡只做了電話攔截,所以對撥打電話沒有做處理,僅僅是列印了log日誌。當不是撥打電話的時候,我們通過context獲取到了TelephonyManager類,通過獲取當前的來電狀態,進行相應的處理,其中CALL_STATE_RINGING是表示來電但是還沒接聽,CALL_STATE_OFFHOOK表示來電並接通了電話,CALL_STATE_IDLE表示結束通話了電話。我們只需要在來電還沒接聽的時候做處理就可以了,具體的處理就是獲取到來電的手機號碼,然後判斷是否是黑名單中的號碼,在isInBlackList函式中進行比較,並且處理了結束通話電話的操作。

2、電話結束通話

在上面的程式碼中我們已經可以監聽到來電的電話號碼,剩下的事情只要判斷是否在黑名單,如果是的話結束通話電話就可以了。判斷是否在黑名單是比較簡單的,我們可以寫一個介面,讓使用者輸入黑名單的號碼,然後把黑名單資訊儲存起來,等到來電話的時候就讀取出來進行對比,這個地方我們不做過多介紹。結束通話電話是比較麻煩的,因為Android的API中是沒有結束通話電話的功能的,那麼我們該如何實現呢?

首先我們需要知道Android原始碼中如何結束通話電話的,那就是用ITelephony的endCall方法,那我們如何得到ITelephony這個物件呢,這個地方就需要用到Android的AIDL,AIDL(Android Interface Definition Language, Android介面定義語言)簡單來說是可以實現程序間通訊的技術,這裡我們需要兩個Android系統原始碼中的兩個檔案,一個是com.android.internal.telephony包下的ITelephony.aidl一個是android.telephony包下的NeighboringCellInfo.aidl,

ITelephony.aidl檔案內容

package com.android.internal.telephony;
interface ITelephony
{
	boolean endCall();
	void answerRingingCall();
}


NeighboringCellInfo.aidl檔案內容

package android.telephony;  
parcelable NeighboringCellInfo;

先建兩個檔案一樣名稱的的包名,然後把這兩個檔案分別放在對應的包下。然後我們就可以用反射的方式獲取到ITelephony類,然後呼叫該類的endCall方法結束通話電話了,具體程式碼如下:
/**
 * 判斷是否是在黑名單中的號碼
 * 
 * @param context
 * 			上下文
 * @param num	來電的號碼
 */
public void isInBlackList(Context context, String num)
{    
    String telstemp[] = DataBean.getInstance().getBlackList();
    for(String temp:telstemp)
    {
        if(temp.contains(num))  
        {
            // 攔截來電
            stop(context, num);
                 
            // 記錄日誌
            Time time = new Time("GMT+8");    
            time.setToNow(); 
                
            String times = time.year+"."+time.month+"."+time.monthDay+" "+time.hour+":"
                				+time.minute+":"+time.second;
            DataBean.getInstance().addTelItem(num + "," + times);
                
            MainListener.getInstance().refreshFragmentList(FragmentTel.FLAGS);
    			
        }
     }
}
	
/**
 * 結束通話
 * 
 * @param context
 * 				上下文環境
 * @param incoming_number
 * 				打來的電話號碼
 */
public void stop(Context context ,String incoming_number) 
{ 
    AudioManager mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    //靜音處理
    mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
    //獲取電話介面

    ITelephony iTelephony = getITelephony(context);
    try
    {
        iTelephony.endCall();//結束電話
    }
    catch (RemoteException e)
    {
        e.printStackTrace();
    }
    //再恢復正常鈴聲
    mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}

/**
* 獲取系統的電話例項
*
* @param context
* 上下文
* @return 電話例項
 */
private static ITelephony getITelephony(Context context)
{
    ITelephony iTelephony = null;
    TelephonyManager telephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    try
    {
        Method getITelephonyMethod = TelephonyManager.class.getDeclaredMethod("getITelephony", (Class[]) null);
        getITelephonyMethod.setAccessible(true);
        iTelephony = (ITelephony) getITelephonyMethod.invoke(telephonyMgr, (Object[]) null);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    return iTelephony;
}


解釋一下上面程式碼,isInBlackList方法中通過自定義的DataBean獲取到了之前儲存的黑名單資料,然後判斷當前來電是否存在黑名單中,如果存在,則呼叫stop方法,然後記錄日誌,並重新整理攔截到的資訊列表。其中stop方法中我們是先獲取到電話的音訊管理器,然後設定靜音,用getITelephony獲取ITelephony物件,然後呼叫endCall方法結束電話,最後把靜音設定回去。getITelephony方法裡面也比較簡單,就不多說了。

到目前為止我們監聽到了來電並且把來電結束通話了,實現了我們的電話攔截功能,同時也儲存了日誌資訊。下面我們介紹如何攔截簡訊訊息。

二、簡訊攔截

簡訊攔截其實是比較簡單的一種,網上有很多相關介紹,這裡主要介紹一下在寫的過程中遇到的一些問題。

首先簡訊攔截的實現原理也是監聽簡訊的廣播,然後判斷簡訊的號碼,判斷是否是在黑名單,如果是的話就用abortBroadcast方法結束廣播的傳遞就可以了。其中在做的過程中最主要的問題不是收到監聽,而且最先收到監聽,因為簡訊的廣播是有序廣播,那麼誰最先收到廣播,誰就可以有權利結束廣播的傳遞,所以我們實際上在做的時候是要想辦法把我們接收廣播的許可權提到最高,這裡主要是兩個方法,1是設定許可權值最大,2是註冊方式設定為動態註冊的。

設定許可權最大,其實就是把註冊廣播時的優先順序設定最大,其中Android系統api中說明最大許可權是1000,而在實際上接收的是一個int值,而且系統沒有判斷值的上線,所以我們可以設定int的最大值,這個許可權是最高的。

那麼如果都是最大許可權了,誰的優先順序高呢,那麼就是第二點,動態註冊監聽,因為在原始碼中動態註冊的廣播是在靜態廣播之前放入監聽列表中的,所以我們這裡用動態註冊來設定監聽。

那如果大家也都用動態註冊的方式了,誰先收到呢,這個地方好像又跟應用的包名有關係,具體我也沒有搞太明白,大概意思是安裝時間越早,優先順序越高,包名在系統中的別名順序越靠前,優先順序越高(不是我們寫的包名的字母排序)。

那麼我們來看一下具體實現,首先是建一個service,然後保證這個service一直在後臺執行(可以用守護程序,監聽開機廣播等等方式保證一直存在),然後在service的onStartCommand和onDestroy方法中分別註冊廣播和取消註冊。

private BroadcastReceiver smsReceive = new SmsReceive();
    
@Override
public IBinder onBind(Intent arg0) 
{
    return null;
}

@Override
public void onCreate()
{
    super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
    // 註冊簡訊監聽廣播
    registerSmsReceiver();
    return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy()
{
    // 解綁簡訊監聽廣播
    unRegisterSmsReceiver();
    super.onDestroy();
}
	
/**
 * 註冊簡訊監聽廣播
 */
private void registerSmsReceiver()
{
    IntentFilter filter = new IntentFilter();  
    filter.addAction(SmsReceive.ACTION_SMS_RECEIVE);  
    filter.setPriority(Integer.MAX_VALUE);  
    registerReceiver(smsReceive, filter);  
}
	
/**
 * 解綁簡訊監聽
 */
private void unRegisterSmsReceiver()
{
    unregisterReceiver(smsReceive);
}

其中SmsReceive是繼承自BroadcastReceiver的廣播接收器,程式碼如下:
/**
 * 簡訊廣播的監聽
 * 
 * @author jeden
 *
 */
public class SmsReceive extends BroadcastReceiver
{
    public static final String ACTION_SMS_RECEIVE = "android.provider.Telephony.SMS_RECEIVED";
	
    public void onReceive(Context context, Intent intent) 
    {   
	String actionName = intent.getAction();
	if (actionName.equals(ACTION_SMS_RECEIVE))
	{   
	    StringBuffer SMSAddress = new StringBuffer();
	    StringBuffer SMSContent = new StringBuffer();
			
	    Bundle bundle = intent.getExtras();
	    if (bundle != null) 
	    {    
		Object[] myOBJpdus = (Object[]) bundle.get("pdus");
		SmsMessage[] messages = new SmsMessage[myOBJpdus.length];
		for (int i = 0; i < myOBJpdus.length; i++) 
		{
		    messages[i] = SmsMessage
				.createFromPdu((byte[]) myOBJpdus[i]);
		}
		for (SmsMessage message : messages) 
		{
		     SMSAddress.append(message  
		                .getDisplayOriginatingAddress());  
		     SMSContent.append(message.getDisplayMessageBody());  
		     Tool.BfLog( "收到的簡訊::"+"來訊號碼:" + SMSAddress + "\n簡訊內容:"  
		                    + SMSContent);
		     String[] telstemp = DataBean.getInstance().getBlackList();
	             for(String temp:telstemp)
	             {
	                 if(temp.contains(SMSAddress))
	            	 {
	            	     // 新增攔截資訊
	            	    DataBean.getInstance().addMsgItem(SMSAddress + "," + SMSContent);
	            			
	            	    MainListener.getInstance().refreshFragmentList(FragmentMsg.FLAGS);
	            			
	    	            abortBroadcast();
	            	 }
	             }
	          }
	       }
	   }
     }
}

其中新增攔截資訊和重新整理列表是自己做了一個頁面用來展示攔截到的資訊的。

ok,這樣電話攔截和簡訊攔截功能就實現了,功能還是比較簡單的!