![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLz1WbvwVbvNmLuRGZ19Gbj5CdrJmLmhza5gjNh12bvw1LcpDc0RHaiojIsJye.png)
- 發送前的校驗
從短信的點選按鈕開始着手:
// packages/apps/Mms/src/com/android/mms/ui/ComposeMessageActivity.java
@Override
public void onClick(View v) {
mIsRTL = (v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
if ((v == mSendButtonSms || v == mSendButtonMms) && isPreparedForSending()) {
... ...
if (mShowTwoButtons) {
confirmSendMessageIfNeeded(SubscriptionManager.getSubId(PhoneConstants.SUB1)[]);
} else {
if (mIsRcsEnabled) {
if (RcsUtils.isRcsOnline()
&& ((mWorkingMessage.getCacheRcsMessage() || (!mWorkingMessage
.requiresMms() && mWorkingMessage.hasText())))) {
rcsSend();
} else {
if (mConversation.isGroupChat()) {
toast(R.string.rcs_offline_unable_to_send);
return;
}
if (mIsBurnMessage) {
toast(R.string.rcs_not_online_can_not_send_burn_message);
mIsBurnMessage = false;
return;
} else {
confirmSendMessageIfNeeded();
}
}
} else {
confirmSendMessageIfNeeded();
}
}
}
... ...
}
首先判斷是否是雙卡的情況,雙卡的情況下通過SubId來區分哪張卡;單卡則判斷是否啟用RCS(rcs預設是關閉的,配置開關的路徑:/Mms/res/values/config.xml config_rcs_sms_version)沒有啟用會執行confirmSendMessageIfNeeded對收件人位址資訊确認.
private void confirmSendMessageIfNeeded() {
LogTag.debugD("confirmSendMessageIfNeeded");
if (mRcsShareVcard) {
mWorkingMessage.setRcsType(RcsUtils.RCS_MSG_TYPE_VCARD);
mRcsShareVcard = false;
}
int slot = SubscriptionManager.getSlotId(
SmsManager.getDefault().getDefaultSmsSubscriptionId());
if ((TelephonyManager.getDefault().isMultiSimEnabled() &&
isLTEOnlyMode(slot))
|| (!TelephonyManager.getDefault().isMultiSimEnabled()
&& isLTEOnlyMode())) {
showDisableLTEOnlyDialog(slot);
LogTag.debugD("return for disable LTEOnly");
return;
}
boolean isMms = mWorkingMessage.requiresMms();
int defaultSubId = SubscriptionManager.getDefaultSmsSubscriptionId();
if (!isRecipientsEditorVisible()) {
if (TelephonyManager.getDefault().isMultiSimEnabled()) {
if ((TelephonyManager.getDefault().getPhoneCount()) > &&
MessageUtils.isMsimIccCardActive()) {
if(SmsManager.getDefault().isSMSPromptEnabled()) {
launchMsimDialog(true, isMms);
} else {
sendMsimMessageNotPrompt(true, isMms, defaultSubId);
}
} else {
sendMsimMessageNotPrompt(true, isMms, defaultSubId);
}
} else if (isMmsWithMobileDataOff(isMms, defaultSubId)) {
showMobileDataDisabledDialog();
} else {
sendMessage(true);
}
return;
}
// 判斷收件人位址是否有效
if (mRecipientsEditor.hasInvalidRecipient(isMms)) {
showInvalidRecipientDialog();
} else if (TelephonyManager.getDefault().isMultiSimEnabled()) { // 判斷是否是雙卡的情況
if ((TelephonyManager.getDefault().getPhoneCount()) > &&
MessageUtils.isMsimIccCardActive()) {
if(SmsManager.getDefault().isSMSPromptEnabled()) {
launchMsimDialog(true, isMms);
} else {
sendMsimMessageNotPrompt(true, isMms, defaultSubId);
}
} else {
sendMsimMessageNotPrompt(true, isMms, defaultSubId);
}
} else if (isMmsWithMobileDataOff(isMms, defaultSubId)) { // 判斷是否是彩信,并且資料流量是否打開
showMobileDataDisabledDialog(defaultSubId);
} else {
if (!TextUtils.isEmpty(getString(R.string.mms_recipient_Limit))
&& isMms
&& checkForMmsRecipients(getString(R.string.mms_recipient_Limit), true)) {
return;
}
//擷取收件人位址,發送
// The recipients editor is still open. Make sure we use what's showing there
// as the destination.
ContactList contacts = mRecipientsEditor.constructContactsFromInput(false);
mDebugRecipients = contacts.serialize();
sendMessage(true);
}
}
這裡是對收件人位址檢測等一些狀态判斷,最後通過sendMessage()繼續發送邏輯.
private void sendMessage(boolean bCheckEcmMode) {
LogTag.debugD("sendMessage true");
if (mIsRcsEnabled && hasConvertRcsAttachmentToMmsAndSent()) {
return;
}
// Check message size, if >= max message size, do not send message.
if(checkMessageSizeExceeded()){
LogTag.debugD("MessageSizeExceeded");
return;
}
// 檢查目前是否是ECM模式
if (bCheckEcmMode) {
// TODO: expose this in telephony layer for SDK build
String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE);
if (Boolean.parseBoolean(inEcm)) {
try {
startActivityForResult(
new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null),
REQUEST_CODE_ECM_EXIT_DIALOG);
return;
} catch (ActivityNotFoundException e) {
// continue to send message
Log.e(TAG, "Cannot find EmergencyCallbackModeExitDialog", e);
}
}
}
// Make the recipients editor lost focus, recipients editor will shrink
// and filter useless char in recipients to avoid send sms failed.
if (isRecipientsEditorVisible()
&& mRecipientsEditor.isFocused()
&& !mWorkingMessage.requiresMms()) {
mTextEditor.requestFocus();
}
// 判斷是否有短信正在發送
if (!mSendingMessage) {
if (LogTag.SEVERE_WARNING) {
String sendingRecipients = mConversation.getRecipients().serialize();
if (!sendingRecipients.equals(mDebugRecipients)) {
String workingRecipients = mWorkingMessage.getWorkingRecipients();
if (workingRecipients != null && !mDebugRecipients.equals(workingRecipients)) {
LogTag.warnPossibleRecipientMismatch("ComposeMessageActivity.sendMessage" +
" recipients in window: \"" +
mDebugRecipients + "\" differ from recipients from conv: \"" +
sendingRecipients + "\" and working recipients: " +
workingRecipients, this);
}
}
sanityCheckConversation();
}
// send can change the recipients. Make sure we remove the listeners first and then add
// them back once the recipient list has settled.
removeRecipientsListeners();
// 判斷是否為多個聯系人,然後進入WorkingMessage執行發送
if (mWorkingMessage.getResendMultiRecipients()) {
// If resend sms recipient is more than one, use mResendSmsRecipient
LogTag.debugD("mWorkingMessage send mResendSmsRecipient=" + mResendSmsRecipient);
mWorkingMessage.send(mResendSmsRecipient);
} else {
LogTag.debugD("mWorkingMessage send mDebugRecipients=" + mDebugRecipients);
mWorkingMessage.send(mDebugRecipients);
}
mSentMessage = true;
mSendingMessage = true;
addRecipientsListeners();
mScrollOnSend = true; // in the next onQueryComplete, scroll the list to the end.
}
// But bail out if we are supposed to exit after the message is sent.
if (mSendDiscreetMode || MessageUtils.isMailboxMode()) {
finish();
}
}
WorkingMessage包含所有使用者編輯短信相關的狀态,這裡判斷如果沒有短信正在發送,則通過WorkingMessage發送短信.
/**
* Send this message over the network. Will call back with onMessageSent() once
* it has been dispatched to the telephony stack. This WorkingMessage object is
* no longer useful after this method has been called.
*
* @throws ContentRestrictionException if sending an MMS and uaProfUrl is not defined
* in mms_config.xml.
*/
public void send(final String recipientsInUI) {
long origThreadId = mConversation.getThreadId();
LogTag.debugD("send origThreadId: " + origThreadId);
removeSubjectIfEmpty(true /* notify */);
// Get ready to write to disk.
prepareForSave(true /* notify */);
// Make sure the mConversation has Recipients
checkConversationHasRecipients(recipientsInUI);
// We need the recipient list for both SMS and MMS.
final Conversation conv = mConversation;
String msgTxt = mText.toString();
if (MmsConfig.isRcsEnabled() && shouldSendMessageWithRcsPolicy()) {
sendRcsMessage(recipientsInUI);
return;
}
if (requiresMms() || addressContainsEmailToMms(conv, msgTxt)) { // 發送彩信
// uaProfUrl setting in mms_config.xml must be present to send an MMS.
// However, SMS service will still work in the absence of a uaProfUrl address.
if (MmsConfig.getUaProfUrl() == null) {
String err = "WorkingMessage.send MMS sending failure. mms_config.xml is " +
"missing uaProfUrl setting. uaProfUrl is required for MMS service, " +
"but can be absent for SMS.";
RuntimeException ex = new NullPointerException(err);
Log.e(TAG, err, ex);
// now, let's just crash.
throw ex;
}
// Make local copies of the bits we need for sending a message,
// because we will be doing it off of the main thread, which will
// immediately continue on to resetting some of this state.
final Uri mmsUri = mMessageUri;
final PduPersister persister = PduPersister.getPduPersister(mActivity);
final SlideshowModel slideshow = mSlideshow;
final CharSequence subject = mSubject;
final boolean textOnly = mAttachmentType == TEXT;
LogTag.debugD("Send mmsUri: " + mmsUri);
// 彩信發送線程
// Do the dirty work of sending the message off of the main UI thread.
new Thread(new Runnable() {
@Override
public void run() {
final SendReq sendReq = makeSendReq(conv, subject);
// Make sure the text in slide 0 is no longer holding onto a reference to
// the text in the message text box.
slideshow.prepareForSend();
sendMmsWorker(conv, mmsUri, persister, slideshow, sendReq, textOnly);
updateSendStats(conv);
}
}, "WorkingMessage.send MMS").start();
} else { //發送短信
// Same rules apply as above.
// Add user's signature first if this feature is enabled.
String text = mText.toString();
LogTag.debugD("mText="+text);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mActivity);
if (sp.getBoolean("pref_key_enable_signature", false)) {
String signature = (sp.getString("pref_key_edit_signature", "")).trim();
if (signature.length() > ) {
String sigBlock = "\n" + signature;
if (!text.endsWith(sigBlock)) {
// Signature should be written behind the text in a
// newline while the signature has changed.
text += sigBlock;
}
}
}
final String msgText = text;
// 短信發送線程
new Thread(new Runnable() {
@Override
public void run() {
preSendSmsWorker(conv, msgText, recipientsInUI);
updateSendStats(conv);
}
}, "WorkingMessage.send SMS").start();
}
// update the Recipient cache with the new to address, if it's different
RecipientIdCache.updateNumbers(conv.getThreadId(), conv.getRecipients());
// Mark the message as discarded because it is "off the market" after being sent.
mDiscarded = true;
}
通過網絡發送消息,分别為短信和彩信開啟了發送線程
// Message sending stuff
private void preSendSmsWorker(Conversation conv, String msgText, String recipientsInUI) {
mIsSending = true;
// If user tries to send the message, it's a signal the inputted text is what they wanted.
UserHappinessSignals.userAcceptedImeText(mActivity);
// 短信準備發送的回調(這裡執行了UI的更新)
mStatusListener.onPreMessageSent();
long origThreadId = conv.getThreadId();
// 擷取線程ID(每個收件人對應一個線程ID,threadId也是指一個會話的ID,和sms和thread資料庫中thread_id和_id對應)
// Make sure we are still using the correct thread ID for our recipient set.
long threadId = conv.ensureThreadId();
String semiSepRecipients = conv.getRecipients().serialize();
// recipientsInUI can be empty when the user types in a number and hits send
if (LogTag.SEVERE_WARNING && ((origThreadId != && origThreadId != threadId) ||
((!mResendMultiRecipients && !semiSepRecipients.equals(recipientsInUI)) &&
!TextUtils.isEmpty(recipientsInUI)))) {
String msg = origThreadId != && origThreadId != threadId ?
"WorkingMessage.preSendSmsWorker threadId changed or " +
"recipients changed. origThreadId: " +
origThreadId + " new threadId: " + threadId +
" also mConversation.getThreadId(): " +
mConversation.getThreadId()
:
"Recipients in window: \"" +
recipientsInUI + "\" differ from recipients from conv: \"" +
semiSepRecipients + "\"";
// Just interrupt the process of sending message if recipient mismatch
LogTag.warnPossibleRecipientMismatch(msg, mActivity);
} else { // 發送短信
// just do a regular send. We're already on a non-ui thread so no need to fire
// off another thread to do this work.
if (mResendMultiRecipients) {
sendSmsWorker(msgText, recipientsInUI, threadId);
mResendMultiRecipients = false;
} else {
sendSmsWorker(msgText, semiSepRecipients, threadId);
}
// 删除短信草稿
// Be paranoid and clean any draft SMS up.
deleteDraftSmsMessage(threadId);
}
mIsSending = false;
}
- 拆分多個聯系人執行發送
private void sendSmsWorker(String msgText, String semiSepRecipients, long threadId) {
// 切割收件人位址
String[] dests = TextUtils.split(semiSepRecipients, ";");
LogTag.debugD("sendSmsWorker sending message: recipients=" +
semiSepRecipients + ", threadId=" + threadId);
// 建構MessageSender對象發送短信
MessageSender sender = new SmsMessageSender(mActivity, dests, msgText, threadId,
mCurrentConvSubId);
try {
// 拆分聯系人,儲存到資料庫
sender.sendMessage(threadId);
// Make sure this thread isn't over the limits in message count
Recycler.getSmsRecycler().deleteOldMessagesByThreadId(mActivity, threadId);
} catch (Exception e) {
Log.e(TAG, "Failed to send SMS message, threadId=" + threadId, e);
}
// 短信發送後的回調UI接口(此時短信已被寫入資料庫,重新整理UI資訊清單)
mStatusListener.onMessageSent();
MmsWidgetProvider.notifyDatasetChanged(mActivity);
}
建立MessageSender對象發送短信,同時在發送前後都回調發送狀态的監聽.SmsMessageSender實作MessageSender接口,該接口有三個實作類分别是:
MmsMessageSender: 彩信的發送
SmsSingleRecipientSender: 單個聯系人的短信發送
SmsMessageSender: 拆分多個聯系人,最終通過SmsSingleRecipientSender發送
public boolean sendMessage(long token) throws MmsException {
// In order to send the message one by one, instead of sending now, the message will split,
// and be put into the queue along with each destinations
return queueMessage(token);
}
private boolean queueMessage(long token) throws MmsException {
if ((mMessageText == null) || (mNumberOfDests == )) {
// Don't try to send an empty message.
if (!(mMessageText == null &&
mContext.getResources().getBoolean(R.bool.enable_send_blank_message))) {
throw new MmsException("Null message body or dest.");
}
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
boolean requestDeliveryReport = false;
if (TelephonyManager.getDefault().isMultiSimEnabled()) {
int slotId = SubscriptionManager.getSlotId(mSubId);
if (MessageUtils.isMsimIccCardActive()) {
requestDeliveryReport = prefs.getBoolean((slotId == PhoneConstants.SUB1) ?
MessagingReportsPreferenceActivity.SMS_DELIVERY_REPORT_SUB1 :
MessagingReportsPreferenceActivity.SMS_DELIVERY_REPORT_SUB2,
DEFAULT_DELIVERY_REPORT_MODE);
} else {
requestDeliveryReport = prefs.getBoolean((slotId == PhoneConstants.SUB1) ?
SmsPreferenceActivity.SMS_DELIVERY_REPORT_SIM1 :
SmsPreferenceActivity.SMS_DELIVERY_REPORT_SIM2,
DEFAULT_DELIVERY_REPORT_MODE);
}
} else {
requestDeliveryReport = prefs.getBoolean(
SmsPreferenceActivity.SMS_DELIVERY_REPORT_NO_MULTI,
DEFAULT_DELIVERY_REPORT_MODE);
}
int priority = -;
try {
String priorityStr = PreferenceManager.getDefaultSharedPreferences(mContext).getString(
"pref_key_sms_cdma_priority", "");
priority = Integer.parseInt(priorityStr);
} catch (Exception e) {
Log.w(TAG, "get priority error:" + e);
}
// 周遊收件人,加入發送隊列
for (int i = ; i < mNumberOfDests; i++) {
try {
LogTag.debugD("queueMessage mDests[i]: " + mDests[i] + " mThreadId: " + mThreadId);
// Check to see whether short message count is up to 2000 for cmcc
if (MessageUtils.checkIsPhoneMessageFull(mContext)) {
break;
}
log("updating Database with subId = " + mSubId);
// 加入發送隊列
Sms.addMessageToUri(mSubId, mContext.getContentResolver(),
Uri.parse("content://sms/queued"), mDests[i],
mMessageText, null, mTimestamp,
true /* read */,
requestDeliveryReport,
mThreadId, priority);
MessageUtils.updateThreadAttachTypeByThreadId(mContext, mThreadId);
} catch (SQLiteException e) {
if (LogTag.DEBUG_SEND) {
Log.e(TAG, "queueMessage SQLiteException", e);
}
SqliteWrapper.checkSQLiteException(mContext, e);
}
}
// 通知SmsReceiver,在SmsReceiver中轉發帶有SmsReceiverService.ACTION_SEND_MESSAGE的intent.
Intent intent = new Intent(SmsReceiverService.ACTION_SEND_MESSAGE, null, mContext,
SmsReceiver.class);
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
// Notify the SmsReceiverService to send the message out
mContext.sendBroadcast(intent);
return false;
}
這裡主要是分析收件人的個數,然後把資訊按每個收件人都放到發送隊列資料庫中,通過Sms.addMessageToUri( ... ... )儲存到content://sms/queued資料庫.然後喚起SmsReceiver來處理隊列.
SmsMessageSender的sendMessage()傳回後WorkingMessage會再次回調UI的接口(mStatusListener.onMessageSent())重新整理資訊清單.
// packages/apps/Mms/src/com/android/mms/transaction/SmsReceiver.java
@Override
public void onReceive(Context context, Intent intent) {
onReceiveWithPrivilege(context, intent, false);
}
protected void onReceiveWithPrivilege(Context context, Intent intent, boolean privileged) {
if (!MessageUtils.hasBasicPermissions()) {
Log.d("Mms", "SmsReceiver do not have basic permissions");
return;
}
String action = intent.getAction();
LogTag.debugD("onReceiveWithPrivilege:intent="+intent+"|privileged="+privileged);
// If 'privileged' is false, it means that the intent was delivered to the base
// no-permissions receiver class. If we get an SMS_RECEIVED message that way, it
// means someone has tried to spoof the message by delivering it outside the normal
// permission-checked route, so we just ignore it.
if (!privileged && (Intents.SMS_DELIVER_ACTION.equals(action) ||
"android.cellbroadcastreceiver.CB_AREA_INFO_RECEIVED".equals(action))) {
return;
}
intent.setClass(context, SmsReceiverService.class);
intent.putExtra("result", getResultCode());
beginStartingService(context, intent);
}
SmsReceiver主要負責監聽廣播,然後将消息轉發給SmsReceiverService處理.SmsReceiverService是處理短信收發工作線程,允許我們将傳入的消息存儲到資料庫、更新通知等,而不阻塞SmsReceiver所運作的主線程.
// packages/apps/Mms/src/com/android/mms/transaction/SmsReceiverService.java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!MmsConfig.isSmsEnabled(this)) {
LogTag.debugD("SmsReceiverService: is not the default sms app");
// NOTE: We MUST not call stopSelf() directly, since we need to
// make sure the wake lock acquired by AlertReceiver is released.
SmsReceiver.finishStartingService(SmsReceiverService.this, startId);
return Service.START_NOT_STICKY;
}
// Temporarily removed for this duplicate message track down.
int resultCode = intent != null ? intent.getIntExtra("result", ) : ;
if (resultCode != ) {
LogTag.debugD("onStart: #" + startId + " resultCode: " + resultCode +
" = " + translateResultCode(resultCode));
}
// 處理intent中的資訊
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
return Service.START_NOT_STICKY;
}
該服務啟動後通過mServiceHandler處理intent中的傳來的請求
/**
* Handle incoming transaction requests.
* The incoming requests are initiated by the MMSC Server or by the MMS Client itself.
*/
@Override
public void handleMessage(Message msg) {
int serviceId = msg.arg1;
Intent intent = (Intent)msg.obj;
LogTag.debugD("handleMessage serviceId: " + serviceId + " intent: " + intent);
if (intent != null && MmsConfig.isSmsEnabled(getApplicationContext())) {
String action = intent.getAction();
int error = intent.getIntExtra("errorCode", );
LogTag.debugD("handleMessage action: " + action + " error: " + error);
if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
handleSmsSent(intent, error);
} else if (SMS_DELIVER_ACTION.equals(action)) {
handleSmsReceived(intent, error);
} else if (CB_AREA_INFO_RECEIVED_ACTION.equals(action)) {
handleCbSmsReceived(intent, error);
} else if (ACTION_BOOT_COMPLETED.equals(action)) {
handleBootCompleted();
} else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
handleServiceStateChanged(intent);
} else if (ACTION_SEND_MESSAGE.endsWith(action)) {
// 發送短信
handleSendMessage(intent);
} else if (ACTION_SEND_INACTIVE_MESSAGE.equals(action)) {
handleSendInactiveMessage();
}
}
// NOTE: We MUST not call stopSelf() directly, since we need to
// make sure the wake lock acquired by AlertReceiver is released.
SmsReceiver.finishStartingService(SmsReceiverService.this, serviceId);
}
SmsReceiver轉發的消息都由handleMessage來處理.之前發送廣播使用的是ACTION_SEND_MESSAGE,是以這裡響應處理handleSendMessage方法.
private void handleSendMessage(Intent intent) {
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.getDefaultSmsSubscriptionId());
if (mSending.get(subId) == null) {
mSending.put(subId, false);
}
// 判斷目前是否有發送任務
if (!mSending.get(subId)) {
sendFirstQueuedMessage(subId);
} else {
LogTag.debugD("subId=" + subId + " is in mSending ");
}
}
// Send first queued message of the given subscription
public synchronized void sendFirstQueuedMessage(int subscription) {
boolean success = true;
boolean isExpectMore = false;
// get all the queued messages from the database
final Uri uri = Uri.parse("content://sms/queued");
ContentResolver resolver = getContentResolver();
String where = "sub_id=?";
String[] whereArgs = new String[] {Integer.toString(subscription)};
// 從隊列中取出短信
Cursor c = SqliteWrapper.query(this, resolver, uri,
SEND_PROJECTION, where, whereArgs, "date ASC"); // date ASC so we send out
// in same order the user
// tried to send messages.
if (c != null) {
try {
if (c.moveToFirst()) { // 發送第一條短信
String msgText = c.getString(SEND_COLUMN_BODY);
String address = c.getString(SEND_COLUMN_ADDRESS);
int threadId = c.getInt(SEND_COLUMN_THREAD_ID);
int status = c.getInt(SEND_COLUMN_STATUS);
int msgId = c.getInt(SEND_COLUMN_ID);
int subId = c.getInt(SEND_COLUMN_SUB_ID);
int priority = c.getInt(SEND_COLUMN_PRIORITY);
Uri msgUri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgId);
// Get the information of is there any messages are pending to process.
// If yes, send this inforamtion to framework to control the link and send all
// messages on same link based on the support in framework
if (c.moveToNext()) {
isExpectMore = true;
}
// 通過SmsMessageSender發送單個收件人的短信
SmsMessageSender sender = new SmsSingleRecipientSender(this,
address, msgText, threadId, status == Sms.STATUS_PENDING,
msgUri, subId, isExpectMore);
MessageUtils.markAsNotificationThreadIfNeed(this, threadId, address);
if(priority != -){
((SmsSingleRecipientSender)sender).setPriority(priority);
}
LogTag.debugD("sendFirstQueuedMessage " + msgUri +
", address: " + address +
", threadId: " + threadId);
try {
// 發送
sender.sendMessage(SendingProgressTokenManager.NO_TOKEN);;
mSending.put(subscription, true);
} catch (MmsException e) { //如果發送失敗,嘗試繼續發送隊列中其他的消息
Log.e(TAG, "sendFirstQueuedMessage: failed to send message " + msgUri
+ ", caught ", e);
mSending.put(subscription, false);
messageFailedToSend(msgUri, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
success = false;
// Sending current message fails. Try to send more pending messages
// if there is any.
Intent intent = new Intent(SmsReceiverService.ACTION_SEND_MESSAGE, null,
this,
SmsReceiver.class);
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subscription);
sendBroadcast(intent);
}
}
} finally {
c.close();
}
}
if (success) {
// We successfully sent all the messages in the queue. We don't need to
// be notified of any service changes any longer.
// In case of MSIM don't unregister service state change if there are any messages
// pending for process on other subscriptions. There may be a chance of other
// subscription is register and waiting for sevice state changes to process the message.
if (!(TelephonyManager.getDefault().getPhoneCount() > ) ||
isUnRegisterAllowed(subscription)) {
unRegisterForServiceStateChanges();
}
}
}
這裡從資料庫隊列中讀取第一條,通過SmsSingleRecipientSender的sendMessage()方法發送.
- 區分普通短信和超長短信
// packages/apps/Mms/src/com/android/mms/transaction/SmsSingleRecipientSender.java
public boolean sendMessage(long token) throws MmsException {
if (mMessageText == null) {
// Don't try to send an empty message, and destination should be just
// one.
throw new MmsException("Null message body or have multiple destinations.");
}
SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(mSubId);
ArrayList<String> messages = null;
if ((MmsConfig.getEmailGateway() != null) &&
(Mms.isEmailAddress(mDest) || MessageUtils.isAlias(mDest))) {
// 彩信
String msgText;
msgText = mDest + " " + mMessageText;
mDest = MmsConfig.getEmailGateway();
messages = smsManager.divideMessage(msgText);
} else { // 短信
messages = smsManager.divideMessage(mMessageText); // 根據文本内容拆分超長短信
// remove spaces and dashes from destination number
// (e.g. "801 555 1212" -> "8015551212")
// (e.g. "+8211-123-4567" -> "+82111234567")
mDest = PhoneNumberUtils.stripSeparators(mDest);
mDest = Conversation.verifySingleRecipient(mContext, mThreadId, mDest);
mDest = MessageUtils.checkIdp(mContext, mDest, mSubId);
}
int messageCount = messages.size();
if (messageCount == ) {
if (!mContext.getResources().getBoolean(R.bool.enable_send_blank_message)) {
// Don't try to send an empty message.
throw new MmsException("SmsMessageSender.sendMessage: divideMessage returned " +
"empty messages. Original message is \"" + mMessageText + "\"");
} else {
return sendEmptyMessage();
}
}
boolean moved = Sms.moveMessageToFolder(mContext, mUri, Sms.MESSAGE_TYPE_OUTBOX, );
if (!moved) {
throw new MmsException("SmsMessageSender.sendMessage: couldn't move message " +
"to outbox: " + mUri);
}
ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
for (int i = ; i < messageCount; i++) {
if (mRequestDeliveryReport && (i == (messageCount - ))) { // 為最後一條短信添加送達報告的Intent
// TODO: Fix: It should not be necessary to
// specify the class in this intent. Doing that
// unnecessarily limits customizability.
deliveryIntents.add(PendingIntent.getBroadcast(
mContext, ,
new Intent(
MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION,
mUri,
mContext,
MessageStatusReceiver.class),
));
} else {
deliveryIntents.add(null);
}
// 拆分後的短信發送完後發送該intent,進而接着發送剩餘拆分的短信
Intent intent = new Intent(SmsReceiverService.MESSAGE_SENT_ACTION,
mUri,
mContext,
SmsReceiver.class);
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
int requestCode = ;
if (i == messageCount -) { // 最後一條拆分的短信
// Changing the requestCode so that a different pending intent
// is created for the last fragment with
// EXTRA_MESSAGE_SENT_SEND_NEXT set to true.
requestCode = ;
intent.putExtra(SmsReceiverService.EXTRA_MESSAGE_SENT_SEND_NEXT, true);
}
LogTag.debugD("sendMessage sendIntent: " + intent);
sentIntents.add(PendingIntent.getBroadcast(mContext, requestCode, intent, ));
}
int validityPeriod = getValidityPeriod(mSubId);
// Remove all attributes for CDMA international roaming.
if (mContext.getResources().getBoolean(R.bool.config_ignore_sms_attributes) &&
MessageUtils.isCDMAInternationalRoaming(mSubId)) {
Log.v(TAG, "sendMessage during CDMA international roaming.");
mPriority = -;
deliveryIntents = null;
validityPeriod = -;
}
try {
// 通過SmsManager發送
smsManager.sendMultipartTextMessage(mDest, mServiceCenter, messages,
sentIntents, deliveryIntents, mPriority, isExpectMore, validityPeriod);
} catch (Exception ex) {
Log.e(TAG, "SmsMessageSender.sendMessage: caught", ex);
throw new MmsException("SmsMessageSender.sendMessage: caught " + ex +
" from SmsManager.sendMultipartTextMessage()");
}
return false;
}
這裡主要是根據短信的長度調用SmsManager的方法divideMessage()來拆分長短信,添加資訊發送狀态的廣播分别由MessageStatusReceiver監聽和SmsReceiverService監聽,它們收到廣播後,從Intent中擷取發送和送達狀态,然後更新資料庫中資訊的狀态同時重新整理UI.
- 從Application調用Framework層執行發送
// frameworks/opt/telephony/src/java/android/telephony/SmsManager.java
public void sendMultipartTextMessage(
String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
int priority, boolean isExpectMore, int validityPeriod) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (parts == null || parts.size() < ) {
throw new IllegalArgumentException("Invalid message body");
}
if (parts.size() > ) { // 超長短信
try {
ISms iccISms = getISmsServiceOrThrow();
if (iccISms != null) {
iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
ActivityThread.currentPackageName(), destinationAddress, scAddress,
parts, sentIntents, deliveryIntents, priority, isExpectMore,
validityPeriod);
}
} catch (RemoteException ex) {
// ignore it
}
} else {
// 普通短信
PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
if (sentIntents != null && sentIntents.size() > ) {
sentIntent = sentIntents.get();
}
if (deliveryIntents != null && deliveryIntents.size() > ) {
deliveryIntent = deliveryIntents.get();
}
sendTextMessage(destinationAddress, scAddress, parts.get(),
sentIntent, deliveryIntent, priority, isExpectMore, validityPeriod);
}
}
- 發送普通短信
public void sendTextMessage(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
boolean isExpectMore, int validityPeriod) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
try {
ISms iccISms = getISmsServiceOrThrow();
if (iccISms != null) {
iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
sentIntent, deliveryIntent, priority, isExpectMore, validityPeriod);
}
} catch (RemoteException ex) {
// ignore it
}
}
這裡調用ISms的實作類UiccSmsController對象的sendTextForSubscriberWithOptions()方法執行發送
// frameworks/opt/telephony/src/java/com/android/internal/telephony/UiccSmsController.java
public void sendTextForSubscriberWithOptions(int subId, String callingPackage,
String destAddr, String scAddr, String parts, PendingIntent sentIntents,
PendingIntent deliveryIntents, int priority, boolean isExpectMore,
int validityPeriod) {
IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
if (iccSmsIntMgr != null ) {
iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntents,
deliveryIntents, priority, isExpectMore, validityPeriod);
} else {
Rlog.e(LOG_TAG,"sendTextWithOptions iccSmsIntMgr is null for" +
" Subscription: " + subId);
}
}
這裡實際又通過IccSmsInterfaceManager來執行發送邏輯.
// frameworks/opt/telephony/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
public void sendTextWithOptions(String callingPackage, String destAddr, String scAddr,
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
int priority, boolean isExpectMore, int validityPeriod) {
// 判斷是否聲明發送短信權限
mPhone.getContext().enforceCallingPermission(
Manifest.permission.SEND_SMS,
"Sending SMS message");
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
" text='"+ text + "' sentIntent=" +
sentIntent + " deliveryIntent=" + deliveryIntent +
"validityPeriod" + validityPeriod);
}
// 是否被使用者允許
if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
callingPackage) != AppOpsManager.MODE_ALLOWED) {
return;
}
// 繼續執行發送邏輯
mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
null/*messageUri*/, callingPackage, false /*persistMessage*/, priority,
isExpectMore, validityPeriod);
}
這裡通過mDispatcher繼續執行發送邏輯.SMSDispatcher有三個實作類:CdmaSMSDispatcher、GsmSMSDispatcher、ImsSMSDispatcher.ImsSMSDispatcher相當于是對上面兩個子類的一個包裝,上層調用時主要調用ImsSMSDispatcher中的方法.ImsSMSDispatcher中再去選擇調用的是Gsm還是Cdma中的方法
@Override
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
boolean persistMessage, int priority, boolean isExpectMore, int validityPeriod) {
Rlog.d(TAG, "sendText");
if (isCdmaMo()) {
mCdmaDispatcher.sendText(destAddr, scAddr,
text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
priority, isExpectMore, validityPeriod);
} else {
mGsmDispatcher.sendText(destAddr, scAddr,
text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
priority, isExpectMore, validityPeriod);
}
}
這裡,我們分析GSM網絡的情況.
@Override
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
boolean persistMessage, int priority, boolean isExpectMore, int validityPeriod) {
// 将短信内容封裝成pdu
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null), validityPeriod);
if (pdu != null) {
// 将短信封裝成tracker
HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
messageUri, isExpectMore, text /*fullMessageText*/,
true /*isText*/, validityPeriod, persistMessage, callingPkg);
String carrierPackage = getCarrierAppPackageName();
if (carrierPackage != null) { // 通過運作商的app發送短信
Rlog.d(TAG, "Found carrier package.");
TextSmsSender smsSender = new TextSmsSender(tracker);
smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
} else {
Rlog.v(TAG, "No carrier package.");
sendRawPdu(tracker); // 一般走這裡
}
} else {
Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
}
}
protected void sendRawPdu(SmsTracker tracker) {
HashMap map = tracker.getData(); // 從tracker中解析出map
byte pdu[] = (byte[]) map.get("pdu"); // 從map中解析出pdu
if (mSmsSendDisabled) { // 短信發送被禁止了
Rlog.e(TAG, "Device does not support sending sms.");
tracker.onFailed(mContext, RESULT_ERROR_NO_SERVICE, /*errorCode*/);
return;
}
if (pdu == null) {
Rlog.e(TAG, "Empty PDU");
tracker.onFailed(mContext, RESULT_ERROR_NULL_PDU, /*errorCode*/);
return;
}
// Get calling app package name via UID from Binder call
PackageManager pm = mContext.getPackageManager();
String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
if (packageNames == null || packageNames.length == ) {
// Refuse to send SMS if we can't get the calling package name.
Rlog.e(TAG, "Can't get calling app package name: refusing to send SMS");
tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, /*errorCode*/);
return;
}
// Get package info via packagemanager
PackageInfo appInfo;
try {
// XXX this is lossy- apps can share a UID
appInfo = pm.getPackageInfo(packageNames[], PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS");
tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, /*errorCode*/);
return;
}
// checkDestination() returns true if the destination is not a premium short code or the
// sending app is approved to send to short codes. Otherwise, a message is sent to our
// handler with the SmsTracker to request user confirmation before sending.
if (checkDestination(tracker)) {
// check for excessive outgoing SMS usage by this app
if (!mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS)) {
sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
return;
}
if (mUsageMonitor.isSmsAuthorizationEnabled()) {
final SmsAuthorizationCallback callback = new SmsAuthorizationCallback() {
@Override
public void onAuthorizationResult(final boolean accepted) {
if (accepted) {
sendSms(tracker);
} else {
tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE,
SmsUsageMonitor.ERROR_CODE_BLOCKED);
}
}
};
mUsageMonitor.authorizeOutgoingSms(tracker.mAppInfo, tracker.mDestAddress,
tracker.mFullMessageText, callback, this);
} else {
sendSms(tracker); //發送短信
}
}
if (PhoneNumberUtils.isLocalEmergencyNumber(mContext, tracker.mDestAddress)) {
new AsyncEmergencyContactNotifier(mContext).execute();
}
}
這裡對要發送的資訊及目前環境進行檢測,然後進入sendSms()流程
// GsmSMSDispatcher.java
@Override
protected void sendSms(SmsTracker tracker) {
HashMap<String, Object> map = tracker.getData();
byte pdu[] = (byte[]) map.get("pdu");
if (tracker.mRetryCount > ) {
Rlog.d(TAG, "sendSms: "
+ " mRetryCount=" + tracker.mRetryCount
+ " mMessageRef=" + tracker.mMessageRef
+ " SS=" + mPhone.getServiceState().getState());
// per TS 23.040 Section 9.2.3.6: If TP-MTI SMS-SUBMIT (0x01) type
// TP-RD (bit 2) is 1 for retry
// and TP-MR is set to previously failed sms TP-MR
if ((( & pdu[]) == )) {
pdu[] |= ; // TP-RD
pdu[] = (byte) tracker.mMessageRef; // TP-MR
}
}
Rlog.d(TAG, "sendSms: "
+ " isIms()=" + isIms()
+ " mRetryCount=" + tracker.mRetryCount
+ " mImsRetry=" + tracker.mImsRetry
+ " mMessageRef=" + tracker.mMessageRef
+ " SS=" + mPhone.getServiceState().getState());
// 發送
sendSmsByPstn(tracker);
}
@Override
protected void sendSmsByPstn(SmsTracker tracker) {
int ss = mPhone.getServiceState().getState();
// if sms over IMS is not supported on data and voice is not available...
if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
tracker.onFailed(mContext, getNotInServiceError(ss), /*errorCode*/);
return;
}
HashMap<String, Object> map = tracker.getData();
byte smsc[] = (byte[]) map.get("smsc");
byte[] pdu = (byte[]) map.get("pdu");
//擷取帶有EVENT_SEND_SMS_COMPLETE的Message
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
// sms over gsm is used:
// if sms over IMS is not supported AND
// this is not a retry case after sms over IMS failed
// indicated by mImsRetry > 0
if ( == tracker.mImsRetry && !isIms()) {
if (tracker.mRetryCount > ) {
// per TS 23.040 Section 9.2.3.6: If TP-MTI SMS-SUBMIT (0x01) type
// TP-RD (bit 2) is 1 for retry
// and TP-MR is set to previously failed sms TP-MR
if ((( & pdu[]) == )) {
pdu[] |= ; // TP-RD
pdu[] = (byte) tracker.mMessageRef; // TP-MR
}
} //調用RILJ發送
if (tracker.mRetryCount == && tracker.mExpectMore) {
// 有多條短信在發送
mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
} else { // 正常發送
mCi.sendSMS(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
}
} else { // 通過IMS發送短信
if (!isRetryAlwaysOverIMS()) {
mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), tracker.mImsRetry,
tracker.mMessageRef, reply);
} else {
mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), ,
tracker.mMessageRef, reply);
}
// increment it here, so in case of SMS_FAIL_RETRY over IMS
// next retry will be sent using IMS request again.
tracker.mImsRetry++; //如果是重試,重試次數加1
}
}
這裡的mCi是CommandsInterface接口的實作類RIL.java,是以是調用RIL中去發送短信.這裡會攜帶reply消息,sms發送成功的消息被smsDispather接收,在handleMessage()中調用handleSendComplete()方法.
// frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java
public void sendImsGsmSms (String smscPDU, String pdu, int retry, int messageRef,
Message result) {
RILRequest rr = RILRequest.obtain(RIL_REQUEST_IMS_SEND_SMS, result);
rr.mParcel.writeInt(RILConstants.GSM_PHONE);
rr.mParcel.writeByte((byte)retry);
rr.mParcel.writeInt(messageRef);
constructGsmSendSmsRilRequest(rr, smscPDU, pdu);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
mMetrics.writeRilSendSms(mInstanceId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
SmsSession.Event.Format.SMS_FORMAT_3GPP);
send(rr);
}
sendSMSExpectMore、sendSMS、sendImsGsmSms、sendImsGsmSms這個四個方法大緻相同,都是先構造RILRequest,再調用send方法發送,差別在于傳入的請求類型不同,構造出來的pdu結構不同.最後在RILSender處理傳過來的RILRequest,通過LocalSocket(LocalSocket其通信方式與Socket差不多,隻是LocalSocket沒有跨越網絡邊界)與守護程序RILD通訊(RILD是RIL(Radio Interface Layer) Deamon的簡稱,簡單的說它下面承接GSM/GPRS Modem(電話通信子產品),上面接電話應用相關的Java庫(telephony internal)).