美文网首页
Android S短消息发送流程

Android S短消息发送流程

作者: 无痕1024 | 来源:发表于2022-03-16 20:36 被阅读0次

    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.jianshu.com/p/e880bf9f067c

    相关文章

      网友评论

          本文标题:Android S短消息发送流程

          本文链接:https://www.haomeiwen.com/subject/yvxmdrtx.html