1 入口
查看Messaging app调用的发送SMS的接口,在这里根据短信内容创建deliveryIntents和sentIntents两个List,deliveryIntents用于短信发送状态报告的回调Intent,而sentIntents作为短信发送结果的回调Intent。
/packages/apps/Messaging/src/com/android/messaging/sms/SmsSender.java
if (MmsConfig.get(subId).getSendMultipartSmsAsSeparateMessages()) {
// If multipart sms is not supported, send them as separate messages
for (int i = 0; i < messageCount; i++) {
smsManager.sendTextMessage(dest,
serviceCenter,
messages.get(i),
sentIntents.get(i),
deliveryIntents.get(i));
}
} else {
smsManager.sendMultipartTextMessage(
dest, serviceCenter, messages, sentIntents, deliveryIntents);
}
2 Framework流程
2.1 流程图
以GSM SMS为例,CDMA SMS和IMS SMS类似:
2.2 源码
Messaging App调用Telephony Frameworks的SmsManager API如下方法发送SMS,该方法的参数解释如下:
String destinationAddress : 短消息目标地址
String scAddress : 短消息服务中心地址
String text : 短消息内容
PendingIntent sentIntent : 短消息发送结果回调Intent,当消息发送成功或者失败时,通过该PendingIntent广播。
PendingIntent deliveryIntent : 短消息发送状态报告回调Intent,接收到Delivery Report后通过该PendingIntent广播。
/frameworks/base/telephony/java/android/telephony/SmsManager.java
sendTextMessage() -> sendTextMessageInternal() -> iSms.sendTextForSubscriber()
public void sendTextMessage(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
true /* persistMessage*/, getOpPackageName(), getAttributionTag(),
0L /* messageId */);
}
private void sendTextMessageInternal(String destinationAddress, String scAddress,
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessage, String packageName, String attributionTag, long messageId) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
// We will only show the SMS disambiguation dialog in the case that the message is being
// persisted. This is for two reasons:
// 1) Messages that are not persisted are sent by carrier/OEM apps for a specific
// subscription and require special permissions. These messages are usually not sent by
// the device user and should not have an SMS disambiguation dialog associated with them
// because the device user did not trigger them.
// 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the SEND_SMS
// permission. If we call resolveSubscriptionForOperation from a carrier/OEM app that has
// the correct MODIFY_PHONE_STATE or carrier permissions, but no SEND_SMS, it will throw
// an incorrect SecurityException.
if (persistMessage) {
resolveSubscriptionForOperation(new SubscriptionResolverResult() {
@Override
public void onSuccess(int subId) {
ISms iSms = getISmsServiceOrThrow();
try {
iSms.sendTextForSubscriber(subId, packageName, attributionTag,
destinationAddress, scAddress, text, sentIntent, deliveryIntent,
persistMessage, messageId);
} catch (RemoteException e) {
Log.e(TAG, "sendTextMessageInternal: Couldn't send SMS, exception - "
+ e.getMessage() + " " + formatCrossStackMessageId(messageId));
notifySmsError(sentIntent, RESULT_REMOTE_EXCEPTION);
}
}
@Override
public void onFailure() {
notifySmsError(sentIntent, RESULT_NO_DEFAULT_SMS_APP);
}
});
} else {
// Not persisting the message, used by sendTextMessageWithoutPersisting() and is not
// visible to the user.
ISms iSms = getISmsServiceOrThrow();
try {
iSms.sendTextForSubscriber(getSubscriptionId(), packageName, attributionTag,
destinationAddress, scAddress, text, sentIntent, deliveryIntent,
persistMessage, messageId);
} catch (RemoteException e) {
Log.e(TAG, "sendTextMessageInternal (no persist): Couldn't send SMS, exception - "
+ e.getMessage() + " " + formatCrossStackMessageId(messageId));
notifySmsError(sentIntent, RESULT_REMOTE_EXCEPTION);
}
}
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/SmsController.java
sendTextForSubscriber() -> sendIccText() -> iccSmsIntMgr.sendText()
@Override
public void sendTextForSubscriber(int subId, String callingPackage,
String callingAttributionTag, String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessageForNonDefaultSmsApp, long messageId) {
if (callingPackage == null) {
callingPackage = getCallingPackage();
}
if (!getSmsPermissions(subId).checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
callingPackage, callingAttributionTag, "Sending SMS message")) {
sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
return;
}
long token = Binder.clearCallingIdentity();
SubscriptionInfo info;
try {
info = getSubscriptionInfo(subId);
} finally {
Binder.restoreCallingIdentity(token);
}
if (isBluetoothSubscription(info)) {
sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
} else {
sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
persistMessageForNonDefaultSmsApp, messageId);
}
}
private void sendIccText(int subId, String callingPackage, String destAddr,
String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessageForNonDefaultSmsApp, long messageId) {
Rlog.d(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr"
+ " Subscription: " + subId + " id: " + messageId);
IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
if (iccSmsIntMgr != null) {
iccSmsIntMgr.sendText(callingPackage, destAddr, scAddr, text, sentIntent,
deliveryIntent, persistMessageForNonDefaultSmsApp, messageId);
} else {
Rlog.e(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr is null for"
+ " Subscription: " + subId + " id: " + messageId);
sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
}
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
sendText() -> sendTextInternal() -> mDispatchersController.sendText()
/**
* A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
* This method checks only if the calling package has the permission to send the sms.
* Note: SEND_SMS permission should be checked by the caller of this method
*/
public void sendText(String callingPackage, String destAddr, String scAddr,
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessageForNonDefaultSmsApp, long messageId) {
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
messageId);
}
private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
int validityPeriod, boolean isForVvm, long messageId) {
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr
+ " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent="
+ deliveryIntent + " priority=" + priority + " expectMore=" + expectMore
+ " validityPeriod=" + validityPeriod + " isForVVM=" + isForVvm
+ " " + SmsController.formatCrossStackMessageId(messageId));
}
notifyIfOutgoingEmergencySms(destAddr);
destAddr = filterDestAddress(destAddr);
mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
priority, expectMore, validityPeriod, isForVvm, messageId);
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/SmsDispatchersController.java
sendText() -> mGsmDispatcher.sendText()
public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage,
int priority, boolean expectMore, int validityPeriod, boolean isForVvm,
long messageId) {
if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
messageUri, callingPkg, persistMessage, priority, false /*expectMore*/,
validityPeriod, isForVvm, messageId);
} else {
if (isCdmaMo()) {
mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
messageUri, callingPkg, persistMessage, priority, expectMore,
validityPeriod, isForVvm, messageId);
} else {
mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
messageUri, callingPkg, persistMessage, priority, expectMore,
validityPeriod, isForVvm, messageId);
}
}
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java extends SMSDispatcher
sendText() -> sendSubmitPdu() -> sendRawPdu() -> sendSms()
public void sendText(String destAddr, String scAddr, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
String callingPkg, boolean persistMessage, int priority,
boolean expectMore, int validityPeriod, boolean isForVvm,
long messageId) {
Rlog.d(TAG, "sendText id: " + messageId);
SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null), null, priority, validityPeriod);
if (pdu != null) {
HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
SmsTracker tracker = getSmsTracker(callingPkg, map, sentIntent, deliveryIntent,
getFormat(), messageUri, expectMore, text, true /*isText*/,
persistMessage, priority, validityPeriod, isForVvm, messageId);
if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) {
sendSubmitPdu(tracker);
}
} else {
Rlog.e(TAG, "SmsDispatcher.sendText(): getSubmitPdu() returned null" + " id: "
+ messageId);
triggerSentIntentForFailure(sentIntent);
}
}
/** Send a single SMS PDU. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void sendSubmitPdu(SmsTracker tracker) {
sendSubmitPdu(new SmsTracker[] {tracker});
}
/** Send a multi-part SMS PDU. Usually just calls {@link sendRawPdu}. */
private void sendSubmitPdu(SmsTracker[] trackers) {
if (shouldBlockSmsForEcbm()) {
Rlog.d(TAG, "Block SMS in Emergency Callback mode");
handleSmsTrackersFailure(trackers, SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY,
NO_ERROR_CODE);
} else {
sendRawPdu(trackers);
}
}
public void sendRawPdu(SmsTracker[] trackers) {
@SmsManager.Result int error = SmsManager.RESULT_ERROR_NONE;
PackageInfo appInfo = null;
if (mSmsSendDisabled) {
Rlog.e(TAG, "Device does not support sending sms.");
error = SmsManager.RESULT_ERROR_NO_SERVICE;
} else {
for (SmsTracker tracker : trackers) {
if (tracker.getData().get(MAP_KEY_PDU) == null) {
Rlog.e(TAG, "Empty PDU");
error = SmsManager.RESULT_ERROR_NULL_PDU;
break;
}
}
if (error == SmsManager.RESULT_ERROR_NONE) {
UserHandle userHandle = UserHandle.of(trackers[0].mUserId);
PackageManager pm = mContext.createContextAsUser(userHandle, 0).getPackageManager();
try {
// Get package info via packagemanager
appInfo =
pm.getPackageInfo(
trackers[0].getAppPackageName(),
PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS"
+ " id: " + getMultiTrackermessageId(trackers));
error = SmsManager.RESULT_ERROR_GENERIC_FAILURE;
}
}
}
if (error != SmsManager.RESULT_ERROR_NONE) {
handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE);
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(trackers)) {
// check for excessive outgoing SMS usage by this app
if (!mSmsDispatchersController
.getUsageMonitor()
.check(appInfo.packageName, trackers.length)) {
sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, trackers));
return;
}
for (SmsTracker tracker : trackers) {
sendSms(tracker);
}
}
if (mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress)) {
new AsyncEmergencyContactNotifier(mContext).execute();
}
}
protected void sendSms(SmsTracker tracker) {
int ss = mPhone.getServiceState().getState();
Rlog.d(TAG, "sendSms: "
+ " isIms()=" + isIms()
+ " mRetryCount=" + tracker.mRetryCount
+ " mImsRetry=" + tracker.mImsRetry
+ " mMessageRef=" + tracker.mMessageRef
+ " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
+ " SS=" + ss
+ " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
// if sms over IMS is not supported on data and voice is not available...
if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
//In 5G case only Data Rat is reported.
if(mPhone.getServiceState().getRilDataRadioTechnology()
!= ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
return;
}
}
Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
HashMap map = tracker.getData();
byte pdu[] = (byte[]) map.get("pdu");
byte smsc[] = (byte[]) map.get("smsc");
if (tracker.mRetryCount > 0) {
// 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 (((0x01 & pdu[0]) == 0x01)) {
pdu[0] |= 0x04; // TP-RD
pdu[1] = (byte) tracker.mMessageRef; // TP-MR
}
}
// 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 OR
// this tracker uses ImsSmsDispatcher to handle SMS over IMS. This dispatcher has received
// this message because the ImsSmsDispatcher has indicated that the message needs to
// fall back to sending over CS.
if (0 == tracker.mImsRetry && !isIms() || tracker.mUsesImsServiceForIms) {
if (tracker.mRetryCount == 0 && tracker.mExpectMore) {
mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
} else {
mCi.sendSMS(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), reply);
}
} else {
mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
IccUtils.bytesToHexString(pdu), tracker.mImsRetry,
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++;
}
}
3 RILD流程
4 命令AT+CMGS
3GPP 27.005介绍了该AT命令:
4 PDU Mode
4.3 Send Message +CMGS
如下是一条具体的例子:
> AT+CMGS=23, "0001000B813142166977F100000B417919947FD741EFF50F"
< +CMGS: 15
< OK
AT+CMGS命令携带长度和PDU;返回的+CMGS命令携带MR。
5 Modem流程
3GPP 24.011,以LTE S1 mode下发送SMS为例:
协议分层 | Step1(MS->NW) | Step2(NW->MS) | Step3(NW->MS) | Step4(MS->NW) |
---|---|---|---|---|
SM_AL | SMS-SUBMIT | SMS-SUBMIT REPORT | ||
SM_TL | ||||
SM_RL | RP-DATA | RP-ACK | ||
CM_sublayer | CP-DATA | CP-ACK | CP-DATA | CP-ACK |
EMM_sublayer | Uplink NAS Transport | Downlink NAS transport | Downlink NAS transport | Uplink NAS Transport |
ERRC | ulInformationTransfer | dlInformationTransfer | dlInformationTransfer | ulInformationTransfer |
PDU消息:
Step1:
SMS SUBMIT: 01 0f 0b 81 31 42 16 69 77 f1 00 00 0b 41 79 19 94 7f d7 41 ef f5 0f
Uplink NAS Transport: 07 63 27 09 01 24 00 0f 00 08 91 68 31 10 80 88 05 f0 17 01 0f 0b 81 31 42 16 69 77 f1 00 00 0b 41 79 19 94 7f d7 41 ef f5 0f
Step2:
Downlink NAS transport: 07 62 02 89 04
Step3:
Downlink NAS transport: 07 62 10 89 01 0d 03 0f 41 09 01 00 22 30 61 01 93 45 23
SMS-SUBMIT REPORT: 01 00 22 30 61 01 93 45 23
Step4:
Uplink NAS Transport: 07 63 02 09 04
6 参考文档
SMS发送流程
https://www.freesion.com/article/64721379131/
Android源码
http://aospxref.com/android-12.0.0_r3/
http://aosp.opersys.com/xref/android-12.0.0_r2/
版权声明:本文为 无痕1024 原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.jianshu.com/p/e880bf9f067c