Android 电话和短信拦截

之前用的手机上没有黑名单功能,下载第三方的软件又觉得不安全,所以自己写了一个简单版本的凑合用,在这里记录一下。


因为之前有做过短信的拦截相关功能,但是电话拦截接触也不是很多,所以并没有做过详细的测试(自己在两款手机上跑过,都是ok的),在做这个功能的时候也参考过几篇博客,但是具体地址没记住。


一、电话拦截

电话拦截的实现其实就是由电话的监听和电话的挂断两个部分组成,其中电话监听就是通过注册广播来实现监听的,挂断电话稍微复杂一些,是用到了Android的跨进程调用的AIDL。

1、首先看一下电话监听,我们需要监听android.intent.action.PHONE_STATE这个广播和android.intent.action.NEW_OUTGOING_CALL这个广播,其中一个是来电状态,一个是拨出电话的监听。我们可以在AndroidManifest中静态注册来电广播的接收器,如下:


	
		           
	        
	

然后定义一个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,这样电话拦截和短信拦截功能就实现了,功能还是比较简单的!



你可能感兴趣的:(Android项目开发记录)