一、概述
最近測試送出了一個bug:用我們的應用删除Android5.0手機上的短信失敗。
二、原因分析
在google查閱後得知:Android為了防止第三方軟體攔截短信和偷發短信吸費,在android4.4之後,隻有預設的短信應用才有權限操作短信資料庫。
Android4.4短信機制的改變:
Getting Your SMS Apps Ready for KitKat
4.4 之前:
- 新接收短信廣播 SMS_RECEIVED_ACTION 為有序廣播。任意應用可接到該廣播并中止其繼續傳播。中止後優先級低的短信應用和系統短信服務将不知道新短信到達,進而不寫進資料庫。這樣就做到了攔截(其實很多惡意應用也這麼幹)。
- 任意應用都可以操作短信資料庫,包括建立(含僞造收件箱和發件箱短信)、修改(含篡改曆史短信)、删除。
- 任意應用都可以發送短信和彩信,但預設不寫進短信資料庫,除非應用手動存入,否則使用者是看不到的(配合攔截就可以安靜地吸費了)。
4.4 及之後:
- 設立預設短信應用機制,成為預設短信後的應用将全面接管(替代)系統短信服務。與設定預設浏覽器類似,成為預設短信應用需要向使用者申請。
- 新接收短信廣播 SMS_RECEIVED_ACTION 更改為無序廣播,增加隻有預設短信應用能夠接收的廣播 SMS_DELIVER_ACTION和WAP_PUSH_DELIVER_ACTION 。二者的不同在于,當預設短信應用收到 SMS_DELIVER_ACTION 時它要負責将其存入資料庫。任意應用仍然可以接收到 SMS_RECEIVED_ACTION 廣播但不能将其中止。是以所有的應用和系統短信服務都可以接收到新短信,沒有應用能夠再用中止廣播的方式攔截短信。
- 隻有預設短信應用可以操作短信資料庫,包括建立(含僞造收件箱和發件箱短信)、修改(含篡改曆史短信)和删除。其它應用隻能讀取短信資料庫。預設短信應用需要在發送短信、收到新短信之後手動寫入系統短信資料庫,否則其它應用将讀取不到該條短信。預設短信應用可以通過控制不寫入資料庫的方式攔截短信。
- 任意應用仍然都可以發送短信,但預設短信應用以外的應用發短信的接口底層改為調用系統短信服務,而不再直接調用驅動通信,是以其所發短信會被系統短信服務自動轉存資料庫。此外,隻有預設短信應用可以發送彩信。
簡單來說,第三方非預設短信應用:
1. 可以收短信、發短信并接收短信回執,但是不能删除短信
2. 可以查詢短信資料庫,但是不能新增、删除、修改短信資料庫
3. 無法攔截短信
但是!極少數國産手機廠商會修改這個機制,實際測試中發現小米就修改了這個機制!小米4,android6.0系統,miui穩定版8.2,運作非預設應用,居然還是可以删除短信。但是别的小米手機又是不行的。非常奇怪。
三、如何解決
提示使用者設定自己的app為default SMS app
為了使我們的應用出現在系統設定的Default SMS app中,我們需要在Manifest中做一些聲明,擷取對應的權限:
1. 聲明一個 broadcast receiver控件,對SMS_DELIVER_ACTION廣播進行監聽,當然這個receiver也要聲明BROADCAST_SMS權限。
2. 聲明一個 broadcast receiver控件,對WAP_PUSH_DELIVER_ACTION廣播進行監聽,當然這個receiver也要聲明BROADCAST_WAP_PUSH權限。
3. 在短信發送界面,需要監聽 ACTION_SENDTO,同時配置上sms:, smsto:, mms:, and mmsto這四個概要,這樣别的應用如果想發送短信,你的這個activity就能知道。
4. 需要有一個service,能夠監聽ACTION_RESPONSE_VIA_MESSAGE,同時也要配置上sms:, smsto:, mms:, and mmsto這四個概要,并且要聲明SEND_RESPOND_VIA_MESSAGE權限。這樣使用者就能在來電的時候,用你的應用來發送拒絕短信。
<manifest>
...
<application>
<!-- BroadcastReceiver that listens for incoming SMS messages -->
<receiver android:name=".SmsReceiver"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
<!-- BroadcastReceiver that listens for incoming MMS messages -->
<receiver android:name=".MmsReceiver"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<!-- Activity that allows the user to send new SMS/MMS messages -->
<activity android:name=“.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</activity>
<!-- Service that delivers messages from the phone "quick response" -->
<service android:name=".HeadlessSmsSendService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
</application>
</manifest>
通過 Telephony.Sms.getDefaultSmsPackage()方法來判斷自己的應用是否為Default SMS app。如果不是,可以通過startActivity() 方法啟動類似如下的Dialog。
public class MainActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
final String myPackageName = getPackageName();
if (!Telephony.Sms.getDefaultSmsPackage(this).equals(myPackageName)) {
// App is not default.
// Show the "not currently set as the default SMS app" interface
View viewGroup = findViewById(R.id.ll_not_default_app);
viewGroup.setVisibility(View.VISIBLE);
// Set up a button that allows the user to change the default SMS app
View button = findViewById(R.id.btn_change_default_app);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, myPackageName);
startActivity(intent);
}
});
} else {
// App is the default.
// Hide the "not currently set as the default SMS app" interface
View viewGroup = findViewById(R.id.ll_not_default_app);
viewGroup.setVisibility(View.GONE);
}
}

四、源碼下載下傳:
https://github.com/wenzhiming/520sms
五、參考資料
- 預設短信應用
- Getting Your SMS Apps Ready for KitKat
- android4.4短信處理機制發生了那些變化?