Android S短消息发送流程

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类似:
sendSmsFramework.png

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<String, Object> 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流程

sendSmsRild.png

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.cnblogs.com/wuhen1024/p/16014413.html

posted @ 2022-03-16 20:48  无痕1024  阅读(673)  评论(0编辑  收藏  举报