为了看代码方便,一边在网上google资料,一边看Android java 源代码。

偶然发现了一个类MmsSmsDatabaseHelper.java,原来android将所有的短信信息都存入了mmssms.db中。

公开的SDK中没有这个类,不能直接使用。于是自己写了一个SQLiteOpenHelper,但是查询的时候发生SQL异常。

看来不能为所欲为了,不过据网上资料介绍可以拷贝db文件来实现短信数据备份。

既然每个db跟package名相关,建立了一个package为com.android.providers.telephony的工程去试一试,看看能不能成功。

结果输出Please execute 'adb uninstall com.android.providers.telephony' in a shell,android的安全做得很强大啊。

不能直接访问数据库,只能通过协议来访问数据库了,       

先贴出相关的协议:       

content://sms/inbox        收件箱

content://sms/sent        已发送

content://sms/draft        草稿

content://sms/outbox        发件箱

content://sms/failed        发送失败

content://sms/queued        待发送列表

在模拟器上Outbox没有查询到数据,在模拟器上找了老半天也没找到发件箱,很郁闷。   

数据库中sms相关的字段如下:   

_id               一个自增字段,从1开始

thread_id    序号,同一发信人的id相同

address      发件人手机号码

person        联系人列表里的序号,陌生人为null

date            发件日期

protocol      协议,分为: 0 SMS_RPOTO, 1 MMS_PROTO 

read           是否阅读 0未读, 1已读 

status         状态 -1接收,0 complete, 64 pending, 128 failed

type    

    ALL    = 0;
    INBOX  = 1;
    SENT   = 2;
    DRAFT  = 3;
    OUTBOX = 4;
    FAILED = 5;
    QUEUED = 6;

body                     短信内容

service_center     短信服务中心号码编号

subject                  短信的主题

reply_path_present     TP-Reply-Path

locked    

检索数据方法很简单:  


12345678910

​Uri uri = Uri.parse(​​​​"content://sms/inbox"​​​​);        ​​​​Cursor cur = ​​​​this​​​​.managedQuery(uri, ​​​​null​​​​, ​​​​null​​​​, ​​​​null​​​​, ​​​​null​​​​);        ​​​​if​​​​(cur.moveToFirst()) {        ​​​​    ​​​​do​​​​{    ​​​​    ​​​​for​​​​(​​​​int​​​​j = ​​​​0​​​​; j < cur.getColumnCount(); j++){    ​​​​            ​​​​info = ​​​​"name:"​​​​+ cur.getColumnName(j) + ​​​​"="​​​​+ cur.getString(j);​​​​            ​​​​Log.i(​​​​"====>"​​​​, info);​​​​        ​​​​}​​​​    ​​​​}​​​​while​​​​(cur.moveToNext());     ​​​​}​


managedQuery最终也要将参数转换为SQL语句向SQLite发送消息,因此参数跟SQL语句很类似,所以可以在查询字段中加入SQL函数,

比如new String[] projection = new String[]{"count(*) as count"}等等。      

managedQuery中的参数依次为uri,       

查询字段          查询字段数组,也可以将所有需要查询的字段放入一个字符内   

                      比如new projection[]{"_id", "thread_id"}和new projection[]{"_id,thread_id"}是一致的。   

                      跟SQL一样,字段名不区分大小写   

条件                不带Where的SQL 条件字符,如果有参数则用?替代,比如"_id=? And thread_id = ? Or type = '1'"   

条件中的参数   参数字符数组,跟上述的条件一一对应   

排序                不带Order by排序字符串,比如_id desc, type   

如果参数为null,SQL中查询字段为“*”,相关的条件为空白

还可以用getContentResolver()获得一个ContentResolver,   

getContentResolver().query()同样返回一个Cursor对象,参数跟managedQuery一致。   

不过用ContentResolver对象去更新、删除和插入一条数据时报SecurityException。看来没有权限,在Manifest.xml中加入权限:


1

​<​​​​uses-permission​​​​android:name​​​​=​​​​"android.permission.WRITE_SMS"​​​​></​​​​uses-permission​​​​>​


然后删除短信:


1

​this​​​​.getContentResolver().delete(Uri.parse(​​​​"content://sms"​​​​), ​​​​"_id=?"​​​​, ​​​​new​​​​String[]{​​​​"3"​​​​});​


删除成功。

Url中content://sms 替换成content://sms/ 也成功,但是其它url时程序报错,比如content://sms/inbox

看了一下android的源代码,sms支持的协议有:


12345678910111213141516171819202122232425

​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​null​​​​, SMS_ALL);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"#"​​​​, SMS_ALL_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"inbox"​​​​, SMS_INBOX);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"inbox/#"​​​​, SMS_INBOX_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"sent"​​​​, SMS_SENT);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"sent/#"​​​​, SMS_SENT_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"draft"​​​​, SMS_DRAFT);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"draft/#"​​​​, SMS_DRAFT_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"outbox"​​​​, SMS_OUTBOX);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"outbox/#"​​​​, SMS_OUTBOX_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"undelivered"​​​​, SMS_UNDELIVERED);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"failed"​​​​, SMS_FAILED);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"failed/#"​​​​, SMS_FAILED_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"queued"​​​​, SMS_QUEUED);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"conversations"​​​​, SMS_CONVERSATIONS);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"conversations/*"​​​​, SMS_CONVERSATIONS_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"raw"​​​​, SMS_RAW_MESSAGE);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"attachments"​​​​, SMS_ATTACHMENT);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"attachments/#"​​​​, SMS_ATTACHMENT_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"threadID"​​​​, SMS_NEW_THREAD_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"threadID/*"​​​​, SMS_QUERY_THREAD_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"status/#"​​​​, SMS_STATUS_ID);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"sr_pending"​​​​, SMS_STATUS_PENDING);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"sim"​​​​, SMS_ALL_SIM);​​​​sURLMatcher.addURI(​​​​"sms"​​​​, ​​​​"sim/#"​​​​, SMS_SIM);​


其中,delete方法中支持的协议为:

SMS_ALL               根据参数中的条件删除sms表数据

SMS_ALL_ID         根据_id删除sms表数据

SMS_CONVERSATIONS_ID     根据thread_id删除sms表数据,可以带其它条件

SMS_RAW_MESSAGE              根据参数中的条件删除 raw表

SMS_STATUS_PENDING         根据参数中的条件删除 sr_pending表

SMS_SIM                                 从Sim卡上删除数据

试一下SMS_CONVERSATIONS_ID:"content://sms/conversations/3 ",删除thread_id="3", _id="5"的数据       

在eclipse中的Emulator Control中,以13800给模拟器发送三条数据,然后以13900发送一条       

this.getContentResolver().delete(Uri.parse("content://sms/conversations/3"), "_id=?", new String[]{"5"});        

成功删除一条数据。   

在数据库中每个发送者的thread_id虽然一样,但不是固定的,如果把一个发送者的全部数据删除掉,       

然后换一个新号码发送短信时,thread_id是以数据库中最大的id+1赋值的。   

update支持的协议有很多:

SMS_RAW_MESSAGE   
SMS_STATUS_PENDING   
SMS_ALL   
SMS_FAILED   
SMS_QUEUED   
SMS_INBOX   
SMS_SENT   
SMS_DRAFT   
SMS_OUTBOX   
SMS_CONVERSATIONS   
SMS_ALL_ID   
SMS_INBOX_ID   
SMS_FAILED_ID   
SMS_SENT_ID   
SMS_DRAFT_ID   
SMS_OUTBOX_ID   
SMS_CONVERSATIONS_ID   
SMS_STATUS_ID 
  

以SMS_INBOX_ID测试一下:   


123456

​ContentValues cv = ​​​​new​​​​ContentValues();   ​​​​cv.put(​​​​"thread_id"​​​​, ​​​​"2"​​​​);   ​​​​cv.put(​​​​"address"​​​​, ​​​​"00000"​​​​);   ​​​​cv.put(​​​​"person"​​​​, ​​​​"11"​​​​);   ​​​​cv.put(​​​​"date"​​​​, ​​​​"11111111"​​​​);   ​​​​this​​​​.getContentResolver().update(Uri.parse(​​​​"content://sms/inbox/4"​​​​), cv, ​​​​null​​​​, ​​​​null​​​​);​


太强了,连thread_id都可以修改。   

insert支持的协议:

SMS_ALL   
SMS_INBOX   
SMS_FAILED   
SMS_QUEUED   
SMS_SENT   
SMS_DRAFT   
SMS_OUTBOX   
SMS_RAW_MESSAGE   
SMS_STATUS_PENDING   
SMS_ATTACHMENT   
SMS_NEW_THREAD_ID  
 

向sms表插入数据时,type是根据协议来自动设置,   

如果传入的数据中没有设置date时,自动设置为当前系统时间;非SMS_INBOX协议时,read标志设置为1   

SMS_INBOX协议时,系统会自动查询并设置PERSON   

threadId为null或者0时,系统也会自动设置   

一直为造不了"发送失败"的邮件而发愁,现在来做一个:   

content://sms/failed   


12345678910111213

​ContentValues cv = ​​​​new​​​​ContentValues();   ​​​​cv.put(​​​​"_id"​​​​, ​​​​"99"​​​​);   ​​​​cv.put(​​​​"thread_id"​​​​, ​​​​"0"​​​​);   ​​​​cv.put(​​​​"address"​​​​, ​​​​"9999"​​​​);   ​​​​cv.put(​​​​"person"​​​​, ​​​​"888"​​​​);   ​​​​cv.put(​​​​"date"​​​​, ​​​​"9999"​​​​);​​​​cv.put(​​​​"protocol"​​​​, ​​​​"0"​​​​);​​​​cv.put(​​​​"read"​​​​, ​​​​"1"​​​​);​​​​cv.put(​​​​"status"​​​​, ​​​​"-1"​​​​);​​​​//cv.put("type", "0");​​​​cv.put(​​​​"body"​​​​, ​​​​"@@@@@@@@@"​​​​);​


​this​​​​.getContentResolver().insert(Uri.parse(​​​​"content://sms/failed"​​​​), cv);​



type被设置成了5,thread_id设置为1


系统连最起码的数据校验都没有做啊,google对程序员也太仁慈了。

看看能不能再挖掘一下sms的功能。先来做一个错误的查询:

getContentResolver().query( Uri.parse("content://sms/") , new String[]{"a"}, "b", null, null);

log输出错误的SQL语句:

SELECT a FROM sms WHERE (b) ORDER BY date DESC

query方法中没有Group by,如果想对短信做统计,对Cursor进行遍历再统计也太慢了。

在SQL语言中group by在Where后面,那就在条件参数中想想办法:

Android组织SQL语句时将条件两端加(),那就拼一个group by出来吧:

getContentResolver().query( Uri.parse("content://sms/") , new String[]{"count(*) as count, thread_id"}, "1=1) group by (thread_id", null, null);

那么输出的SQL= SELECT count(*) as count, thread_id FROM sms WHERE ( 1=1) group by (thread_id ) ORDER BY date DESC

如果想查询URI没有对应的表怎么办呢,比如想知道 mmssms.db数据库中有哪些表,

查询的表是URI定的,再在条件参数中拼凑肯定是不行。

那我们把目光往前移,看看在字段参数中能不能凑出来。

要查询其它表,关键要去掉系统固定添加的FROM sms,

用用SQL中的注释吧,

getContentResolver().query(Uri.parse("content://sms/"), new String[]{" * from sqlite_master WHERE type = 'table' -- "}, null, null, null);

那么输出的SQL=SELECT * from sqlite_master WHERE type = 'table' -- FROM sms ORDER BY date DESC

居然能够运行。


得寸进尺,再进一步,如果加入“;”也能运行的话,哈哈,那么建表、删除表、更新表也能为所欲为咯。

getContentResolver().query(Uri.parse("content://sms/"), new String[]{" * from sms;select * from thrreads;-- "}, null, null, null);

很可惜,只运行了第一条SQL语句,看来在关键问题上,android还是有所控制的。

不过支持--也很不错了,这样可以查询数据库中所有的表,而且还可以多表联查