4.4版本之前:
清单:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sg31.smswriter"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
代码:
package com.sg31.smswriter;
import android.support.v7.app.ActionBarActivity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public void writeSMSBtnClicked(View v) {
Thread t = new Thread() {
@Override
public void run() {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put("address", 95555);
values.put("type", 1);
values.put("date", System.currentTimeMillis());
values.put("body",
"贵帐户*9093于"+"1月12号11:58"+"转入13145.20元【交通银行】");
Uri newuri = cr.insert(Uri.parse("content://sms"), values);
System.out.println(newuri.toString());
}
};
t.start();
}
}
4.4版本之后:
原文地址:Getting Your SMS Apps Ready for KitKat
发送和接收短信是手机最基本的功能,很多的开发者也开发了很多成功的应用来增强Android这一方面的体验。你们当中的某些人可能基于隐藏API来开发短信应用,这种做法我们是不推荐的,因为隐藏API可能会有改变或者被移除,这样新的设备可能无法通过兼容性测试。因此,为了让你能够用到全面支持的API集来开发短信应用,以及短信应用体验的可预见性,Android 4.4 (KitKat)公开了现有的API并且增加了默认短信应用的概念,也就是说用户可以在系统设置中进行选择默认短信。
这意味着如果你在之前的Android版本中使用了隐藏API,你需要对你的应用进行一些调整以便让你的应用可以在今年晚些时候Android 4.4发布之后能够在Android4.4上继续使用。
让你的应用成为默认短信
在Android 4.4上,只有一个应用能接收到新增的SMS_DELIVER_ACTION
intent,这个intent是当系统接收到新短信时用来发送广播的。哪个应用接收这个广播取决于用户在系统设置里选择了哪个应用作为默认短信应用。同样的,也只有默认的短信应用能在系统接收到新彩信的时候接收到Android 4.4新增加的WAP_PUSH_DELIVER_ACTION
intent。
其他只想读取新信息的应用可以接收SMS_RECEIVED_ACTION
广播,这个广播也是系统接收到新短信时发送的。但是只有接收到SMS_DELIVER_ACTION
的应用(用户指定的默认短信应用)可以写入由android.provider.Telephony
类和子类定义的SMS Provider。因此,尽快升级你的短信应用让它可以成为默认短信应用是很重要的,因为就算你现有的应用不会在Android 4.4的设备上crash,但它尝试写入SMS Provider的时候会在没有提示的情况下失败。
为了让你的应用在系统设置中作为一个可选的默认短信应用,你的manifest文件必须声明一些特定的内容。所以你必须为你的应用更新以下内容:
-
在一个broadcast receiver中包含一个
SMS_DELIVER_ACTION
("android.provider.Telephony.SMS_DELIVER")的intent filter。这个broadcast receiver同样需要BROADCAST_SMS
权限。这让你的应用可以直接接收到进来的短信。
-
在一个broadcast receiver包含一个
WAP_PUSH_DELIVER_ACTION
("android.provider.Telephony.WAP_PUSH_DELIVER")的intent filter,MIME类型是"application/vnd.wap.mms-message"。这个broadcast receiver同样需要BROADCAST_WAP_PUSH
权限。这让你的应用可以直接接收到进来的彩信。
-
在你用来发送新信息的activity中包含一个
ACTION_SENDTO
("android.intent.action.SENDTO")的intent filter,schemas为sms:, smsto:, mms:, 和mmsto:。这让你的应用可以接收到其他想发送信息的应用请求的intent。
-
在一个service里面包含一个
ACTION_RESPONSE_VIA_MESSAGE
("android.intent.action.RESPOND_VIA_MESSAGE")的intent filter,schemas是sms:, smsto:, mms:, 和 mmsto:。这个service同样需要SEND_RESPOND_VIA_MESSAGE
权限。这让用户在来电的时候用你的应用进行即时的短信息回复。
举个例子,这是一个含有必要组件和intent filter的manifest文件:
| |
现有所有接收带SMS_RECEIVED_ACTION
这个filter的广播的应用可以照常的在Android 4.4上工作,但只能作为新信息的查看者,因为除非你的应用也能接收到SMS_DELIVER_ACTION
广播,否则你无法在Android 4.4上写入SMS Provider。
从Android 4.4开始,你应该停止监听SMS_RECEIVED_ACTION
广播,你可以通过运行时来检查Android平台的版本,然后通过PackageManager.setComponentEnabledSetting()
来禁用掉接收SMS_RECEIVED_ACTION
的broadcast receiver。当然如果你的应用只需要读取一些特定的短信息比如用来做电话号码校验的,你也可以继续监听这个广播。如果你在Android 4.4上确实需要处理SMS_RECEIVED_ACTION
这个intent,那么不要调用API取消掉这个broadcast,因为别的应用可能也会需要接收这个广播。
提示:要区分这两个短信广播,可以想象成SMS_RECEIVED_ACTION
只是简单地说“嘿,系统接收到一条短信”而SMS_DELIVER_ACTION
却是说“系统传递给你的应用一条短信,因为你是系统默认的短信应用”。
当不是默认短信应用时禁用掉相应的功能
当你的应用不是当前系统默认的短信应用,那么把你应用发送新信息的功能禁用掉是很重要的。因为你无法写入SMS Provider,所有你发送的信息在用户默认的短信应用中都不可见。所以当你的activity resume的时候你可以通过查询Telephony.Sms.getDefaultSmsPackage()
来检查你的应用是不是默认的短信应用,这个方法返回当前默认短信应用的包名。如果结果跟你的包名不匹配,那么就把发送信息的功能禁用掉。
要让你的应用能够接收和发送信息,你可以弹出一个让用户可以选择你的应用作为默认短信应用的系统对话框。要弹出这个对话框,调用startActivity()
,用Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT
这个intent作为参数,这个intent包含一对key-value值,分别是Sms.Intents.EXTRA_PACKAGE_NAME
和你的包名。
要提供一个良好的用户体验,可以在你的activity resume的时候检查默认的短信应用,并且修改你的UI,把让用户修改默认短信应用这样一个信息给包括进来。比如,你的Activity可能会包括像下面这样的代码:
| |
给短信备份还原类应用的建议
由于写SMS Provider这样一个功能局限在用户选择的默认短信应用,当前所有只为短信备份还原设计的应用在Android 4.4上会无法还原短信。一个备份还原短信的应用同样需要被设置成默认的短信应用才能写入短信到SMS Provider当中去。当然,一个应用如果没有发送和接收短信的功能,那么它也不应该一直被设置成默认短信应用。所以当用户打开你的应用准备做一次性还原操作时,你可以根据以下的设计提供一个功能性的UI体验:
-
查询当前默认的短信应用包名并把包名保存起来。
String defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(context);
-
请求用户把你的应用设置成默认短信应用以便进行短信还原(你必须作为默认短信应用才可以写入数据到SMS Provider中)。
| |
- 当你还原完所有短信之后,请求用户把步骤一保存的应用设置回默认短信应用。
| |
准备升级你的短信应用
为了给你的用户提供最好的Android体验,我们建议你尽可能快的升级你的应用。为了帮助你做出调整,我们马上会提供Android 4.4必要的SDK组件以便让你能编译和测试你在Android 4.4上做出的修改。敬请期待!
在Android4.4之前的版本,往短信箱插入信息很方便,所以这个对用户来说很有威胁的漏洞,在Android4.4得到了修复。Android4.4中只有手机默认的消息App才能处理和短信相关的操作,而手机默认的消息App一般就是手机里自带的官方App,当然用户可以在设置里面,手动地将自己信任的消息App设置为默认App,总的来说,短信的操作控制权掌握到用户自己的手中。
那如何自己实现一个处理消息的App呢?可以参考这篇文章。让你的短信应用迎接Android 4.4(KitKat)。这篇文章是根据Google给出的官方文档翻译下来的。里面对消息App的必须的实现细节已经做了详细的说明。
下面是我自己用到的时候具体的代码:
MainActivity.java
1 package tina.messagebox;
2
3 import android.content.ContentResolver;
4 import android.content.ContentUris;
5 import android.content.ContentValues;
6 import android.content.Intent;
7 import android.database.Cursor;
8 import android.net.Uri;
9 import android.provider.Telephony;
10 import android.support.v7.app.ActionBarActivity;
11 import android.os.Bundle;
12 import android.text.method.MovementMethod;
13 import android.text.method.ScrollingMovementMethod;
14 import android.view.Menu;
15 import android.view.MenuItem;
16 import android.view.View;
17 import android.widget.Button;
18 import android.widget.EditText;
19 import android.widget.TextView;
20 import android.widget.Toast;
21
22 import java.text.SimpleDateFormat;
23 import java.util.Date;
24
25
26 public class MainActivity extends ActionBarActivity {
27 private String defaultSmsPkg;
28 private String mySmsPkg;
29 private TextView mMessageView=null;
30 private EditText mPhoneNumber=null;
31 private EditText mMsg=null;
32
33 @Override
34 protected void onCreate(Bundle savedInstanceState) {
35 super.onCreate(savedInstanceState);
36 setContentView(R.layout.activity_main);
37
38 mPhoneNumber=(EditText)findViewById(R.id.editText);
39 mMsg=(EditText)findViewById(R.id.editText2);
40 mMessageView=(TextView)findViewById(R.id.textView3);
41 mMessageView.setMovementMethod(ScrollingMovementMethod.getInstance()); //设置滚动
42 defaultSmsPkg= Telephony.Sms.getDefaultSmsPackage(this);
43 mySmsPkg= this.getPackageName();
44
45 if(!defaultSmsPkg.equals(mySmsPkg)){
46 // 如果这个App不是默认的Sms App,则修改成默认的SMS APP
47 // 因为从Android 4.4开始,只有默认的SMS APP才能对SMS数据库进行处理
48 Intent intent=new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
49 intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME,mySmsPkg);
50 startActivity(intent);
51 }
52
53 Button button=(Button)findViewById(R.id.button);
54 button.setOnClickListener(new View.OnClickListener() {
55 @Override
56 public void onClick(View v) {
57 if(mySmsPkg.equals(Telephony.Sms.getDefaultSmsPackage(MainActivity.this))){
58 if(mPhoneNumber.getText().toString().isEmpty()){
59 Toast.makeText(MainActivity.this,"Phone number cannot be empty!",Toast.LENGTH_LONG).show();
60 return;
61 }
62 if(mMsg.getText().toString().isEmpty()){
63 Toast.makeText(MainActivity.this,"Message cannot be empty!",Toast.LENGTH_LONG).show();
64 return;
65 }
66 System.out.println("My App is default SMS App.");
67 // 对短信数据库进行处理
68 ContentResolver resolver=getContentResolver();
69
70 ContentValues values=new ContentValues();
71 values.put(Telephony.Sms.ADDRESS,mPhoneNumber.getText().toString());
72 values.put(Telephony.Sms.DATE, System.currentTimeMillis());
73 long dateSent=System.currentTimeMillis()-5000;
74 values.put(Telephony.Sms.DATE_SENT,dateSent);
75 values.put(Telephony.Sms.READ,false);
76 values.put(Telephony.Sms.SEEN,false);
77 values.put(Telephony.Sms.STATUS, Telephony.Sms.STATUS_COMPLETE);
78 values.put(Telephony.Sms.BODY, mMsg.getText().toString());
79 values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_INBOX);
80
81 Uri uri=resolver.insert(Telephony.Sms.CONTENT_URI,values);
82 if(uri!=null){
83 long uriId= ContentUris.parseId(uri);
84 System.out.println("uriId "+uriId);
85 }
86
87 Toast.makeText(MainActivity.this, "Insert a short Message.",
88 Toast.LENGTH_LONG).show();
89
90 // 对短信数据库处理结束后,恢复原来的默认SMS APP
91 Intent intent=new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
92 intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME,defaultSmsPkg);
93 startActivity(intent);
94 System.out.println("Recover default SMS App");
95
96 // 打印出收件箱里的最新5条短信
97 Cursor cursor=getContentResolver().query(Telephony.Sms.CONTENT_URI,null,null,null,null);
98 String msg="";
99 while ((cursor.moveToNext()) &&
100 (cursor.getPosition()<5)){
101 int dateColumn=cursor.getColumnIndex("date");
102 int phoneColumn=cursor.getColumnIndex("address");
103 int smsColumn=cursor.getColumnIndex("body");
104
105 System.out.println("count "+cursor.getCount()+" position "+cursor.getPosition());
106 // 把从短信中获取的时间戳换成一定格式的时间
107 SimpleDateFormat sfd=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
108 Date date=new Date(Long.parseLong(cursor.getString(dateColumn)));
109 String time=sfd.format(date);
110 msg=msg+time+" "+cursor.getString(phoneColumn)+":"+cursor.getString(smsColumn)+"\n";
111 mMessageView.setText(msg);
112 }
113
114 }
115 else{
116 Toast.makeText(MainActivity.this,"Sorry,the App is not default Sms App.",
117 Toast.LENGTH_LONG).show();
118 }
119 }
120 });
121 }
122
123 @Override
124 public boolean onCreateOptionsMenu(Menu menu) {
125 // Inflate the menu; this adds items to the action bar if it is present.
126 getMenuInflater().inflate(R.menu.menu_main, menu);
127 return true;
128 }
129
130 @Override
131 public boolean onOptionsItemSelected(MenuItem item) {
132 // Handle action bar item clicks here. The action bar will
133 // automatically handle clicks on the Home/Up button, so long
134 // as you specify a parent activity in AndroidManifest.xml.
135 int id = item.getItemId();
136
137 //noinspection SimplifiableIfStatement
138 if (id == R.id.action_settings) {
139 return true;
140 }
141
142 return super.onOptionsItemSelected(item);
143 }
144 }
以上是具体的动作实现,要完成一个默认的消息App,还需要具备接收SMS和MMS的能力以及发送SMS的能力,所以还需要MmsReceiver/SmsReceiver/SmsSendService,具体到今天要实现的功能而言,这几个能力都不用具体实现,所以Receiver和Service并不用对具体的action进行相应。
MmsReceiver.java
1 package tina.messagebox;
2
3 import android.content.BroadcastReceiver;
4 import android.content.Context;
5 import android.content.Intent;
6
7 /**
8 * Created by Tina on 2015/8/11.
9 */
10 public class MmsReceiver extends BroadcastReceiver {
11 @Override
12 public void onReceive(Context context, Intent intent) {
13
14 }
15 }
SmsReceiver.java
1 package tina.messagebox;
2
3 import android.content.BroadcastReceiver;
4 import android.content.Context;
5 import android.content.Intent;
6
7 /**
8 * Created by Tina on 2015/8/11.
9 */
10 public class SmsReceiver extends BroadcastReceiver {
11 @Override
12 public void onReceive(Context context, Intent intent) {
13
14 }
15 }
SmsSendService.java
1 package tina.messagebox;
2
3 import android.app.Service;
4 import android.content.Intent;
5 import android.os.IBinder;
6
7 public class SmsSendService extends Service {
8 public SmsSendService() {
9 }
10
11 @Override
12 public IBinder onBind(Intent intent) {
13 // TODO: Return the communication channel to the service.
14 return null;
15 }
16 }
最后,要在AndroidManifest.xml中对Receiver和Service进行注册,并声明消息相关的权限。
1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="tina.messagebox" >
4
5 <!-- Adding -->
6 <uses-permission android:name="android.permission.WRITE_SMS" />
7 <uses-permission android:name="android.permission.READ_SMS" />
8
9 <!-- End Adding -->
10 <application
11 android:allowBackup="true"
12 android:icon="@mipmap/ic_launcher"
13 android:label="@string/app_name"
14 android:theme="@style/AppTheme" >
15 <activity
16 android:name=".MainActivity"
17 android:label="@string/app_name" >
18 <intent-filter>
19 <action android:name="android.intent.action.MAIN" />
20
21 <category android:name="android.intent.category.LAUNCHER" />
22
23 <!-- Adding -->
24 <action android:name="android.intent.action.SEND" />
25 <action android:name="android.intent.action.SENDTO" />
26
27 <category android:name="android.intent.category.DEFAULT" />
28 <category android:name="android.intent.category.BROWSABLE" />
29
30 <data android:scheme="sms" />
31 <data android:scheme="smsto" />
32 <data android:scheme="mms" />
33 <data android:scheme="mmsto" />
34 <!-- End Adding -->
35
36 </intent-filter>
37 </activity>
38
39 <!-- Adding -->
40 <!-- BroadcastReceiver that listens for incoming SMS messages -->
41 <receiver
42 android:name=".SmsReceiver"
43 android:permission="android.permission.BROADCAST_SMS" >
44 <intent-filter>
45 <action android:name="android.provider.Telephony.SMS_DELIVER" />
46 </intent-filter>
47 </receiver>
48
49 <!-- BroadcastReceiver that listens for incoming MMS messages -->
50 <receiver
51 android:name=".MmsReceiver"
52 android:permission="android.permission.BROADCAST_WAP_PUSH" >
53 <intent-filter>
54 <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
55
56 <data android:mimeType="application/vnd.wap.mms-message" />
57 </intent-filter>
58 </receiver>
59
60 <!-- Service that delivers messages from the phone "quick response" -->
61 <service
62 android:name=".SmsSendService"
63 android:exported="true"
64 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
65 <intent-filter>
66 <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
67
68 <category android:name="android.intent.category.DEFAULT" />
69
70 <data android:scheme="sms" />
71 <data android:scheme="smsto" />
72 <data android:scheme="mms" />
73 <data android:scheme="mmsto" />
74 </intent-filter>
75 </service>
76 <!-- End Adding -->
77 </application>
78
79 </manifest>
这两个Receiver和Service缺了任何一个,App都无法成为一个默认的消息App。
下面,是今天实现的App的运行效果。第一次打开App,会检测该App是否是默认消息App,如果不是,则弹出对话框让用户决定是否将其设置为默认App,如果用户同意,则可以输入手机号码和短信内容,按一下Insert,手机短信箱里就会多了一条短信,插入成功后会再弹出一个对话框,询问用户是否恢复手机自带的短信App为默认,用户选择Yes,那么回到Message中,就可以看到一条未读短信啦。当然,万一一不小心,在后面这个对话框里选择了NO,那么为了能够正常地处理短信,还是到手机的“设置”里面,把原来的App设为默认吧。