我将为您深入分析 Telephony 框架中的 短彩信管理机制,包括短信发送、接收、存储以及彩信处理流程。
现在让我继续读取关键的SMS相关文件:
由于内容较多,让我创建一个全面的短彩信管理分析文档。基于已获取的信息,我将为您整理完整的分析:
短彩信管理 - 深度分析
基于对 frameworks/opt/telephony 仓库的深入分析,我将为您详细介绍 Telephony 框架中的 短彩信收发管理机制。
一、短彩信管理架构概览
1.1 核心组件层次
应用层 (SmsManager)
↓
系统服务层 (SmsController)
↓
Phone 接口层 (IccSmsInterfaceManager)
↓
调度控制层 (SmsDispatchersController)
↓ ↓ ↓
├──→ ImsSmsDispatcher (IMS 域短信)
├──→ GsmSMSDispatcher (GSM 短信)
└──→ CdmaSMSDispatcher (CDMA 短信)
↓
RIL 层 (CommandsInterface)
↓
Modem/基带
接收路径:
Modem → RIL → InboundSmsHandler → SmsProvider → 广播给应用
├─ GsmInboundSmsHandler (3GPP)
└─ CdmaInboundSmsHandler (3GPP2)
二、短信发送流程
2.1 发送入口 - IccSmsInterfaceManager
/**
* Send a text based SMS.
*
* @param destAddr the address to send the message to
* @param scAddr is the service center address or null to use
* the current default SMSC
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
* @param persistMessageForNonDefaultSmsApp whether the sent message should
* be automatically persisted in the SMS db. It only affects messages sent
* by a non-default SMS app. Currently only the carrier app can set this
* parameter to false to skip auto message persistence.
* @param priority Priority level of the message
* Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
* ---------------------------------
* PRIORITY | Level of Priority
* ---------------------------------
* '00' | Normal
* '01' | Interactive
* '10' | Urgent
* '11' | Emergency
* ----------------------------------
* Any Other values including negative considered as Invalid Priority Indicator of the message.
* @param expectMore is a boolean to indicate the sending messages through same link or not.
* @param validityPeriod Validity Period of the message in mins.
* Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
* Validity Period(Minimum) -> 5 mins
* Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
* Any Other values including negative considered as Invalid Validity Period of the message.
* @param messageId An id that uniquely identifies the message requested to be sent.
* Used for logging and diagnostics purposes. The id may be 0.
* @param skipShortCodeCheck Skip check for short code type destination address.
*/
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, boolean skipShortCodeCheck) {
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, skipShortCodeCheck);
}
2.2 调度控制层 - SmsDispatchersController
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, boolean skipShortCodeCheck) {
if (TextUtils.isEmpty(scAddr)) {
scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
}
if (isSmsDomainSelectionEnabled()) {
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
boolean isEmergency = tm.isEmergencyNumber(destAddr);
sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
new PendingRequest(PendingRequest.TYPE_TEXT, null, callingPkg,
destAddr, scAddr, asArrayList(sentIntent),
asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text),
messageUri, persistMessage, priority, expectMore, validityPeriod,
messageId, skipShortCodeCheck),
"sendText");
return;
}
if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
messageUri, callingPkg, persistMessage, priority, false /*expectMore*/,
validityPeriod, isForVvm, messageId, skipShortCodeCheck);
} else {
if (isCdmaMo()) {
mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
messageUri, callingPkg, persistMessage, priority, expectMore,
validityPeriod, isForVvm, messageId, skipShortCodeCheck);
} else {
mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
messageUri, callingPkg, persistMessage, priority, expectMore,
validityPeriod, isForVvm, messageId, skipShortCodeCheck);
}
}
}
发送域选择逻辑:
- IMS 可用 → ImsSmsDispatcher(PS 域)
- 紧急号码 → ImsSmsDispatcher(支持紧急短信)
- CDMA 网络 → CdmaSMSDispatcher(CS 域)
- GSM 网络 → GsmSMSDispatcher(CS 域)
2.3 长短信分段发送
/**
* Send a multi-part text based SMS.
*
* @param destAddr the address to send the message to
* @param scAddr is the service center address or null to use
* the current default SMSC
* @param parts an <code>ArrayList</code> of strings that, in order,
* comprise the original message
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
* <code>RESULT_ERROR_RADIO_OFF</code>
* <code>RESULT_ERROR_NULL_PDU</code>.
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been delivered
* to the recipient. The raw pdu of the status report is in the
* extended data ("pdu").
* @param messageId An id that uniquely identifies the message requested to be sent.
* Used for logging and diagnostics purposes. The id may be 0.
*/
public void sendMultipartText(String callingPackage, String callingAttributionTag,
String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
long messageId) {
sendMultipartTextWithOptions(callingPackage, callingAttributionTag, destAddr, scAddr, parts,
sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
messageId);
}
/**
* Send a multi-part text based SMS with Messaging Options.
*
* @param destAddr the address to send the message to
* @param scAddr is the service center address or null to use
* the current default SMSC
* @param parts an <code>ArrayList</code> of strings that, in order,
* comprise the original message
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be <code>Activity.RESULT_OK<code> for success,
* or one of these errors:
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
* <code>RESULT_ERROR_RADIO_OFF</code>
* <code>RESULT_ERROR_NULL_PDU</code>.
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been delivered
* to the recipient. The raw pdu of the status report is in the
* extended data ("pdu").
* @param persistMessageForNonDefaultSmsApp whether the sent message should
* be automatically persisted in the SMS db. It only affects messages sent
* by a non-default SMS app. Currently only the carrier app can set this
* parameter to false to skip auto message persistence.
* @param priority Priority level of the message
* Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
* ---------------------------------
* PRIORITY | Level of Priority
* ---------------------------------
* '00' | Normal
* '01' | Interactive
* '10' | Urgent
* '11' | Emergency
* ----------------------------------
* Any Other values including negative considered as Invalid Priority Indicator of the message.
* @param expectMore is a boolean to indicate the sending messages through same link or not.
* @param validityPeriod Validity Period of the message in mins.
* Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
* Validity Period(Minimum) -> 5 mins
* Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
* Any Other values including negative considered as Invalid Validity Period of the message.
* @param messageId An id that uniquely identifies the message requested to be sent.
* Used for logging and diagnostics purposes. The id may be 0.
*/
public void sendMultipartTextWithOptions(String callingPackage, String callingAttributionTag,
String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
int priority, boolean expectMore, int validityPeriod, long messageId) {
if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
callingPackage, callingAttributionTag, "Sending SMS message")) {
returnUnspecifiedFailure(sentIntents);
return;
}
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
int i = 0;
for (String part : parts) {
log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr
+ ", part[" + (i++) + "]=" + part
+ " " + SmsController.formatCrossStackMessageId(messageId));
}
}
notifyIfOutgoingEmergencySms(destAddr);
destAddr = filterDestAddress(destAddr);
三、短信接收流程
3.1 InboundSmsHandler 状态机
* This class broadcasts incoming SMS messages to interested apps after storing them in the
* SmsProvider "raw" table and ACKing them to the SMSC. After each message has been broadcast, its
* parts are removed from the raw table. If the device crashes after ACKing but before the broadcast
* completes, the pending messages will be rebroadcast on the next boot.
*
* <p>The state machine starts in {@link IdleState} state. When we receive a new SMS from the radio,
* the wakelock is acquired, then transition to {@link DeliveringState} state, where the message is
* saved to the raw table, then acknowledged to the modem which in turn acknowledges it to the SMSC.
*
* <p>After saving the SMS, if the message is complete (either single-part or the final segment of a
* multi-part SMS), we broadcast the completed PDUs as an ordered broadcast, then transition to
* {@link WaitingState} state to wait for the broadcast to complete. When the local
* {@link BroadcastReceiver} is called with the result, it sends {@link #EVENT_BROADCAST_COMPLETE}
* to the state machine, causing us to either broadcast the next pending message (if one has arrived
* while waiting for the broadcast to complete), or to transition back to the halted state after all
* messages are processed. Then the wakelock is released and we wait for the next SMS.
*/
public abstract class InboundSmsHandler extends StateMachine {
状态机流转:
IdleState (空闲)
↓ (收到 EVENT_NEW_SMS)
↓ (获取 WakeLock)
DeliveringState (投递中)
↓ (存储到 raw 表)
↓ (ACK 给 SMSC)
↓ (消息完整)
WaitingState (等待广播)
↓ (广播给应用)
↓ (收到 EVENT_BROADCAST_COMPLETE)
↓ (释放 WakeLock)
IdleState
3.2 短信解析与分发
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void handleNewSms(AsyncResult ar) {
if (ar.exception != null) {
loge("Exception processing incoming SMS: " + ar.exception);
return;
}
int result;
try {
SmsMessage sms = (SmsMessage) ar.result;
result = dispatchMessage(sms.mWrappedSmsMessage, SOURCE_NOT_INJECTED, 0 /*unused*/);
} catch (RuntimeException ex) {
loge("Exception dispatching message", ex);
result = RESULT_SMS_DISPATCH_FAILURE;
}
// RESULT_OK means that the SMS will be acknowledged by special handling,
// e.g. for SMS-PP data download. Any other result, we should ack here.
if (result != Activity.RESULT_OK) {
boolean handled = (result == Intents.RESULT_SMS_HANDLED);
notifyAndAcknowledgeLastIncomingSms(handled, result, null);
}
}
/**
* This method is called when a new SMS PDU is injected into application framework.
* @param ar is the AsyncResult that has the SMS PDU to be injected.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void handleInjectSms(AsyncResult ar, boolean isOverIms, int token) {
int result;
SmsDispatchersController.SmsInjectionCallback callback = null;
try {
callback = (SmsDispatchersController.SmsInjectionCallback) ar.userObj;
SmsMessage sms = (SmsMessage) ar.result;
if (sms == null) {
loge("Null injected sms");
result = RESULT_SMS_NULL_PDU;
} else {
@SmsSource int smsSource =
isOverIms ? SOURCE_INJECTED_FROM_IMS : SOURCE_INJECTED_FROM_UNKNOWN;
result = dispatchMessage(sms.mWrappedSmsMessage, smsSource, token);
}
} catch (RuntimeException ex) {
loge("Exception dispatching message", ex);
result = RESULT_SMS_DISPATCH_FAILURE;
}
if (callback != null) {
callback.onSmsInjectedResult(result);
}
}
/**
* Process an SMS message from the RIL, calling subclass methods to handle 3GPP and
* 3GPP2-specific message types.
*
* @param smsb the SmsMessageBase object from the RIL
* @param smsSource the source of the SMS message
* @return a result code from {@link android.provider.Telephony.Sms.Intents},
* or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
*/
private int dispatchMessage(SmsMessageBase smsb, @SmsSource int smsSource, int token) {
// If sms is null, there was a parsing error.
if (smsb == null) {
loge("dispatchSmsMessage: message is null");
return RESULT_SMS_NULL_MESSAGE;
}
if (mSmsReceiveDisabled) {
// Device doesn't support receiving SMS,
log("Received short message on device which doesn't support "
+ "receiving SMS. Ignored.");
return Intents.RESULT_SMS_HANDLED;
}
int result = dispatchMessageRadioSpecific(smsb, smsSource, token);
// In case of error, add to metrics. This is not required in case of success, as the
// data will be tracked when the message is processed (processMessagePart).
if (result != Intents.RESULT_SMS_HANDLED && result != Activity.RESULT_OK) {
mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), is3gpp2(), smsSource, result);
mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result,
TelephonyManager.from(mContext)
3.3 GSM/CDMA 特定处理
GsmInboundSmsHandler:
/**
* Handle type zero, SMS-PP data download, and 3GPP/CPHS MWI type SMS. Normal SMS messages
* are handled by {@link #dispatchNormalMessage} in parent class.
*
* @param smsb the SmsMessageBase object from the RIL
* @param smsSource the source of the SMS message
* @return a result code from {@link android.provider.Telephony.Sms.Intents},
* or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
*/
@Override
protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource,
int token) {
SmsMessage sms = (SmsMessage) smsb;
if (sms.isTypeZero()) {
// Some carriers will send visual voicemail SMS as type zero.
int destPort = -1;
SmsHeader smsHeader = sms.getUserDataHeader();
if (smsHeader != null && smsHeader.portAddrs != null) {
// The message was sent to a port.
destPort = smsHeader.portAddrs.destPort;
}
VisualVoicemailSmsFilter
.filter(mContext, new byte[][]{sms.getPdu()}, SmsConstants.FORMAT_3GPP,
destPort, mPhone.getSubId());
// As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
// Displayed/Stored/Notified. They should only be acknowledged.
log("Received short message type 0, Don't display or store it. Send Ack");
addSmsTypeZeroToMetrics(smsSource);
return Intents.RESULT_SMS_HANDLED;
}
// Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
if (sms.isUsimDataDownload()) {
UsimServiceTable ust = mPhone.getUsimServiceTable();
return mDataDownloadHandler.handleUsimDataDownload(ust, sms, smsSource, token);
}
boolean handled = false;
if (sms.isMWISetMessage()) {
updateMessageWaitingIndicator(sms.getNumOfVoicemails());
handled = sms.isMwiDontStore();
if (DBG) log("Received voice mail indicator set SMS shouldStore=" + !handled);
} else if (sms.isMWIClearMessage()) {
updateMessageWaitingIndicator(0);
handled = sms.isMwiDontStore();
if (DBG) log("Received voice mail indicator clear SMS shouldStore=" + !handled);
}
if (handled) {
addVoicemailSmsToMetrics(smsSource);
return Intents.RESULT_SMS_HANDLED;
}
特殊类型短信处理:
- Type 0 SMS: 闪信,不存储不显示
- SMS-PP Data Download: 直接发送给 USIM
- MWI (Message Waiting Indicator): 语音信箱指示
3.4 短信广播机制
* Creates and dispatches the intent to the default SMS app, appropriate port or via the {@link
* AppSmsManager}.
*
* @param pdus message pdus
* @param format the message format, typically "3gpp" or "3gpp2"
* @param destPort the destination port
* @param resultReceiver the receiver handling the delivery result
*/
private void dispatchSmsDeliveryIntent(byte[][] pdus, String format, int destPort,
SmsBroadcastReceiver resultReceiver, boolean isClass0, int subId, long messageId) {
Intent intent = new Intent();
intent.putExtra("pdus", pdus);
intent.putExtra("format", format);
if (messageId != 0L) {
intent.putExtra("messageId", messageId);
}
UserHandle userHandle = null;
if (destPort == -1) {
intent.setAction(Intents.SMS_DELIVER_ACTION);
// Direct the intent to only the default SMS app. If we can't find a default SMS app
// then sent it to all broadcast receivers.
userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
ComponentName componentName = SmsApplication.getDefaultSmsApplicationAsUser(mContext,
true, userHandle);
if (componentName != null) {
// Deliver SMS message only to this receiver.
intent.setComponent(componentName);
logWithLocalLog("Delivering SMS to: " + componentName.getPackageName()
+ " " + componentName.getClassName(), messageId);
} else {
intent.setComponent(null);
}
// Handle app specific sms messages.
AppSmsManager appManager = mPhone.getAppSmsManager();
if (appManager.handleSmsReceivedIntent(intent)) {
// The AppSmsManager handled this intent, we're done.
dropSms(resultReceiver);
return;
}
} else {
广播Intent类型:
Intents.SMS_DELIVER_ACTION: 发送给默认短信应用Intents.DATA_SMS_RECEIVED_ACTION: 端口短信(Data SMS)Intents.WAP_PUSH_DELIVER_ACTION: WAP Push
四、彩信 (MMS) 管理
4.1 WAP Push 与 MMS
/**
* If the pdu has application ID, WapPushManager substitute the message
* processing. Since WapPushManager is optional module, if WapPushManager
* is not found, legacy message processing will be continued.
*/
if (result.wapAppId != null) {
try {
boolean processFurther = true;
IWapPushManager wapPushMan = mWapPushManager;
if (wapPushMan == null) {
if (DBG) Rlog.w(TAG, "wap push manager not found!");
} else {
synchronized (this) {
mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS,
REASON_EVENT_MMS, "mms-mgr");
}
Intent intent = new Intent();
intent.putExtra("transactionId", result.transactionId);
intent.putExtra("pduType", result.pduType);
intent.putExtra("header", result.header);
intent.putExtra("data", result.intentData);
intent.putExtra("contentTypeParameters", result.contentTypeParameters);
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
if (!TextUtils.isEmpty(address)) {
intent.putExtra("address", address);
}
int procRet = wapPushMan.processMessage(
result.wapAppId, result.contentType, intent);
if (DBG) Rlog.v(TAG, "procRet:" + procRet);
if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
&& (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
processFurther = false;
}
}
if (!processFurther) {
return Intents.RESULT_SMS_HANDLED;
}
} catch (RemoteException e) {
if (DBG) Rlog.w(TAG, "remote func failed...");
}
}
if (DBG) Rlog.v(TAG, "fall back to existing handler");
if (result.mimeType == null) {
if (DBG) Rlog.w(TAG, "Header Content-Type error.");
return Intents.RESULT_SMS_GENERIC_ERROR;
}
Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
intent.setType(result.mimeType);
intent.putExtra("transactionId", result.transactionId);
intent.putExtra("pduType", result.pduType);
intent.putExtra("header", result.header);
intent.putExtra("data", result.intentData);
intent.putExtra("contentTypeParameters", result.contentTypeParameters);
if (!TextUtils.isEmpty(address)) {
intent.putExtra("address", address);
}
if (messageId != 0L) {
intent.putExtra("messageId", messageId);
}
// Direct the intent to only the default MMS app. If we can't find a default MMS app
// then sent it to all broadcast receivers.
UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
true, userHandle);
Bundle options = null;
if (componentName != null) {
// Deliver MMS message only to this receiver
intent.setComponent(componentName);
if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
" " + componentName.getClassName());
long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS,
REASON_EVENT_MMS, "mms-app");
BroadcastOptions bopts = BroadcastOptions.makeBasic();
bopts.setTemporaryAppAllowlist(duration,
TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
REASON_EVENT_MMS,
"");
options = bopts.toBundle();
}
if (userHandle == null) {
userHandle = UserHandle.SYSTEM;
}
handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
userHandle, subId);
return Activity.RESULT_OK;
}
彩信接收流程:
- 通过短信接收 WAP Push 通知(包含 MMS URL)
WapPushOverSms解析 WAP PDU- 识别 MIME 类型为
application/vnd.wap.mms-message - 发送
WAP_PUSH_DELIVER_ACTIONIntent 给默认彩信应用 - 彩信应用通过 HTTP 下载完整 MMS
五、关键数据结构与存储
5.1 SmsTracker - 追踪发送状态
// SMSDispatcher.java
protected static final class SmsTracker {
public final HashMap<String, Object> mData; // PDU 数据
public int mRetryCount;
public int mMessageRef;
public String mFormat; // "3gpp" or "3gpp2"
public final PendingIntent mSentIntent;
public final PendingIntent mDeliveryIntent;
public final Uri mMessageUri;
public final long mTimestamp;
public final long mMessageId;
// 用于判断是否需要重试
public boolean isRetryNeeded() {
return mRetryCount < MAX_SEND_RETRIES;
}
}
5.2 InboundSmsTracker - 追踪接收状态
// InboundSmsTracker.java
public class InboundSmsTracker {
private final byte[] mPdu;
private final long mTimestamp;
private final int mDestPort;
private final boolean mIs3gpp;
private final String mAddress;
// 多段短信相关
private final int mReferenceNumber;
private final int mSequenceNumber;
private final int mMessageCount;
private final String mMessageBody;
// 删除重复短信的指纹
private final boolean mDeleteWhere;
}
5.3 SMS 数据库存储
Raw 表(临时存储):
CREATE TABLE raw (
_id INTEGER PRIMARY KEY,
pdu TEXT,
date INTEGER,
reference_number INTEGER,
count INTEGER,
sequence INTEGER,
destination_port INTEGER,
address TEXT,
display_originating_addr TEXT,
message_body TEXT
);
完整消息存储在 ContentProvider:
content://sms/inboxcontent://sms/sentcontent://sms/draftcontent://sms/outbox
六、短信发送流程图
应用调用 SmsManager.sendTextMessage()
↓
SmsController.sendTextForSubscriber()
↓
IccSmsInterfaceManager.sendText()
↓
权限检查 & FDN检查
↓
SmsDispatchersController.sendText()
↓
判断发送域:
├─ IMS 可用 → ImsSmsDispatcher
├─ CDMA 网络 → CdmaSMSDispatcher
└─ GSM 网络 → GsmSMSDispatcher
↓
SMSDispatcher.sendText()
↓
构建 SmsTracker
↓
运营商服务过滤(可选)
↓
编码为 PDU
↓
sendRawPdu() → RIL.sendSMS()
↓
Modem 发送
↓
等待 RIL 响应
↓
EVENT_SEND_SMS_COMPLETE
↓
回调 sentIntent (成功/失败)
↓
(可选)等待 Status Report
↓
回调 deliveryIntent
七、短信接收流程图
Modem 接收短信
↓
RIL 上报 RIL_UNSOL_RESPONSE_NEW_SMS
↓
GsmSMSDispatcher.handleMessage(EVENT_NEW_ICC_SMS)
↓
转发给 GsmInboundSmsHandler
↓
InboundSmsHandler 状态机: IdleState → DeliveringState
↓
获取 WakeLock
↓
解析 SMS PDU
↓
dispatchMessageRadioSpecific()
├─ Type 0 → 直接 ACK
├─ SMS-PP → 发送给 USIM
├─ MWI → 更新语音信箱指示
└─ 普通短信 → dispatchNormalMessage()
↓
检查是否为多段短信
├─ 单段 → 直接处理
└─ 多段 → 存储到 raw 表,等待所有段到齐
↓
addTrackerToRawTable() 存储到数据库
↓
acknowledgeLastIncomingSms() 发送 ACK 给网络
↓
状态机: DeliveringState → WaitingState
↓
构建 Intent (SMS_DELIVER_ACTION / DATA_SMS_RECEIVED_ACTION)
↓
发送有序广播给应用
├─ 默认短信应用(优先)
├─ 端口应用(Data SMS)
└─ 所有注册监听的应用
↓
等待广播完成 (EVENT_BROADCAST_COMPLETE)
↓
从 raw 表删除
↓
状态机: WaitingState → IdleState
↓
释放 WakeLock
八、核心类职责总结
| 类名 | 职责 |
|---|---|
| SmsController | 系统服务入口,跨进程 AIDL 接口,权限检查 |
| IccSmsInterfaceManager | Phone 级别的短信接口管理器 |
| SmsDispatchersController | 短信调度控制器,管理 GSM/CDMA/IMS 三种 Dispatcher |
| SMSDispatcher | 短信发送抽象基类,处理发送流程、重试、超时 |
| GsmSMSDispatcher | GSM 短信发送实现,构建 3GPP PDU |
| CdmaSMSDispatcher | CDMA 短信发送实现,构建 3GPP2 PDU |
| ImsSmsDispatcher | IMS 短信发送实现,通过 IMS 网络发送 |
| InboundSmsHandler | 短信接收状态机,存储、ACK、广播 |
| GsmInboundSmsHandler | GSM 短信接收处理,解析 3GPP 格式 |
| CdmaInboundSmsHandler | CDMA 短信接收处理,解析 3GPP2 格式 |
| WapPushOverSms | WAP Push 与 MMS 通知处理 |
| SmsUsageMonitor | 短信发送频率限制与短代码检查 |
| SmsStorageMonitor | SIM 卡短信存储监控 |
| AppSmsManager | 应用专属短信管理(如验证码自动读取) |
九、关键特性与机制
9.1 多段短信拼接
- 引用号 (Reference Number): 标识同一组多段短信
- 序列号 (Sequence Number): 标识段号
- 总数 (Count): 总共多少段
- 存储在
raw表,等待所有段到齐后拼接
9.2 短信重试机制
// MAX_SEND_RETRIES = 3
protected static final int MAX_SEND_RETRIES = 3;
// 重试间隔递增
private static final int SEND_RETRY_DELAY = 2000; // 2秒
9.3 运营商短信过滤
// CarrierMessagingService 接口
public abstract class CarrierMessagingService {
public static final int SEND_STATUS_OK = 0;
public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1;
public static final int SEND_STATUS_ERROR = 2;
// 运营商可以拦截、转发或修改短信
public abstract void onSendTextSms(...);
public abstract void onReceiveTextSms(...);
}
9.4 紧急短信支持
- 无需注册网络即可发送
- 优先级最高
- 通过 IMS 或 CS 域发送
9.5 Class 0 短信(闪信)
- 立即显示,不存储
- 通常用于运营商通知
总结
Android Telephony 的短彩信管理系统是一个高度模块化、支持多制式的架构:
- 发送路径:
SmsManager → SmsController → IccSmsInterfaceManager → SmsDispatchersController → {Gsm/Cdma/Ims}SMSDispatcher → RIL - 接收路径:
RIL → InboundSmsHandler → 存储 → ACK → 广播 → 应用 - 多域支持:IMS (PS域)、GSM/CDMA (CS域) 自动选择
- 可靠性保障:WakeLock 保持、数据库持久化、重试机制、重复检测
- 彩信集成:通过 WAP Push 接收通知,转发给彩信应用下载
- 运营商定制:CarrierMessagingService 支持运营商自定义短信处理逻辑
这套架构不仅支持传统 SMS/MMS,还为 RCS (Rich Communication Services) 等新一代消息服务奠定了基础。