4

我开发了一个发送 SMS 消息的应用程序,使用 BroadcastReceivers 成功发送和(未)传递消息。

在我的投递接收者中,我想知道消息被传递给目标收件人的时间。由于发送和接收设备都可以偶尔关闭,我认为将我收到交付广播的时间视为真正的交付时间是不正确的。

有没有办法在我的广播接收器中获得正确的交付时间?谢谢你。

4

2 回答 2

2

这是一个有效的解决方案。

通过研究com.android.internal.telephony.gsm.SmsMessage类的源代码(这是 Android 的内部类,处理在 GSM/3GPP 网络上解析 SMS PDU),我发现SMS-STATUS-REPORTS(=“交付报告”)包含第二个时间戳值,即“放电时间”。不幸的是,这个值没有被公共SmsMessage类公开,它是您想要的交付时间(正如交付网络基础设施所感知的那样)。

使用下面的类,您可以通过调用getDischargeTime().

getServiceCenterTimeStamp()方法返回的值与您从 获得的值相同SmsMessage#getTimestampMillis(),即 SMS 服务中心收到原始消息的时间。

导入 android.telephony.PhoneNumberUtils;导入android.text.format.Time;

/**
 * A helper class to parse (from pdu byte[]) and represent a GSM SMS-STATUS-REPORT message (= delivery report).
 * 
 * Only works for GSM/3GPP networks (not CDMA/3GPP2).
 * 
 * Based on the source code of the following Android classes:
 *  - com.android.internal.telephony.gsm.SmsMessage (almost everything)
 *  - com.android.internal.telephony.uicc.IccUtils (1 method)
 * All licensed under the Apache License, Version 2.0.
 * The code is taken from Android v5.1.0_r1 (+ 1 line from v4.2_r1).
 * 
 * @author mstevens
 */
public class SMSStatusReport
{
    static final String LOG_TAG = SMSStatusReport.class.getSimpleName();

    /**
     * TP-Message-Type-Indicator
     * 9.2.3
     */
    private int mMti;

    /**
     *  TP-Status - status of a previously submitted SMS.
     *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
     *  see TS 23.040, 9.2.3.15 for description of other possible values.
     */
    private int mStatus;

    /**
     *  TP-Status - status of a previously submitted SMS.
     *  This field is true iff the message is a SMS-STATUS-REPORT message.
     */
    private boolean mIsStatusReportMessage = false;

    /**
     * TP-Service-Centre-Time-Stamp
     */
    private long serviceCenterTimeStamp;

    /**
     * TP-Discharge-Time
     */
    private long dischargeTime;

    /**
     * Constructor
     * 
     * @param pdu
     */
    public SMSStatusReport(byte[] pdu)
    {
        // Parse:
        createFromPdu(pdu);

        if(!mIsStatusReportMessage)
            throw new IllegalArgumentException("This is not the pdu of a GSM SMS-STATUS-REPORT message");
    }

    /**
     * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
     * ME/TA converts each octet of TP data unit into two IRA character long
     * hex number (e.g. octet with integer value 42 is presented to TE as two
     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
     * something else...
     * 
     * @param pdu
     * 
     * @see Adapted from {@link com.android.internal.telephony.gsm.SmsMessage#createFromPdu(byte[])} (originally static)
     */
    private void createFromPdu(byte[] pdu)
    {
        PduParser p = new PduParser(pdu);

        /*Object mScAddress = */p.getSCAddress();

        // TP-Message-Type-Indicator
        // 9.2.3
        int firstByte = p.getByte();

        mMti = firstByte & 0x3;
        switch (mMti)
        {
            // TP-Message-Type-Indicator
            // 9.2.3
            case 0:
            case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
                    //This should be processed in the same way as MTI == 0 (Deliver)
                //parseSmsDeliver(p, firstByte);
                break;
            case 1:
                //parseSmsSubmit(p, firstByte);
                break;
            case 2:
                parseSmsStatusReport(p, firstByte);
                break;
            default:
                throw new RuntimeException("Unsupported message type");
        }
    }

    /**
     * Parses a SMS-STATUS-REPORT message.
     *
     * @param p A PduParser, cued past the first byte.
     * @param firstByte The first byte of the PDU, which contains MTI, etc.
     */
    private void parseSmsStatusReport(PduParser p, int firstByte)
    {
        mIsStatusReportMessage = true;

        // TP-Message-Reference
        /*int mMessageRef = */p.getByte();
        // TP-Recipient-Address
        /*Object mRecipientAddress = */p.getAddress();
        // TP-Service-Centre-Time-Stamp
        serviceCenterTimeStamp = p.getSCTimestampMillis();
        // TP-Discharge-Time (line taken from Android v4.2_r1)
        dischargeTime = p.getSCTimestampMillis();
        // TP-Status
        mStatus = p.getByte();

        // The following are optional fields that may or may not be present.
        if (p.moreDataPresent())
        {/*
            // TP-Parameter-Indicator
            int extraParams = p.getByte();
            int moreExtraParams = extraParams;
            while ((moreExtraParams & 0x80) != 0) {
                // We only know how to parse a few extra parameters, all
                // indicated in the first TP-PI octet, so skip over any
                // additional TP-PI octets.
                moreExtraParams = p.getByte();
            }
            // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
            // only process the byte if the reserved bits (bits3 to 6) are zero.
            if ((extraParams & 0x78) == 0) {
                // TP-Protocol-Identifier
                if ((extraParams & 0x01) != 0) {
                    mProtocolIdentifier = p.getByte();
                }
                // TP-Data-Coding-Scheme
                if ((extraParams & 0x02) != 0) {
                    mDataCodingScheme = p.getByte();
                }
                // TP-User-Data-Length (implies existence of TP-User-Data)
                if ((extraParams & 0x04) != 0) {
                    boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
                    parseUserData(p, hasUserDataHeader);
                }
            }*/
        }
    }

    /**
     * @return whether or not the original message was received on the receiver handset
     */
    public boolean isReceived()
    {
        return mStatus == 0;
    }

    /**
     * @return the serviceCenterTimeStamp
     */
    public long getServiceCenterTimeStamp()
    {
        return serviceCenterTimeStamp;
    }

    /**
     * @return the dischargeTime
     */
    public long getDischargeTime()
    {
        return dischargeTime;
    }

    private static class PduParser
    {

        byte mPdu[];
        int mCur;

        PduParser(byte[] pdu)
        {
            mPdu = pdu;
            mCur = 0;
        }

        /**
         * Parse and return the SC address prepended to SMS messages coming via the TS 27.005 / AT interface.
         * Returns null on invalid address
         */
        String getSCAddress()
        {
            int len;
            String ret;

            // length of SC Address
            len = getByte();

            if(len == 0)
            {
                // no SC address
                ret = null;
            }
            else
            {
                // SC address
                try
                {
                    ret = PhoneNumberUtils.calledPartyBCDToString(mPdu, mCur, len);
                }
                catch(RuntimeException tr)
                {
                    ret = null;
                }
            }
            mCur += len;
            return ret;
        }

        /**
         * returns non-sign-extended byte value
         */
        int getByte()
        {
            return mPdu[mCur++] & 0xff;
        }

        /**
         * Any address except the SC address (eg, originating address)
         * See TS 23.040 9.1.2.5
         * 
         * mstevens: Made NON-FUNCTIONAL to remove dependency on internal Android classes. Always returns null but skips right number of bytes.
         */
        Object/*GsmSmsAddress*/ getAddress()
        {
            //GsmSmsAddress ret;

            // "The Address-Length field is an integer representation of
            // the number field, i.e. excludes any semi-octet containing only
            // fill bits."
            // The TOA field is not included as part of this
            int addressLength = mPdu[mCur] & 0xff;
            int lengthBytes = 2 + (addressLength + 1) / 2;

            /*try {
                ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
            } catch (ParseException e) {
                ret = null;
                //This is caught by createFromPdu(byte[] pdu)
                throw new RuntimeException(e.getMessage());
            }*/

            mCur += lengthBytes;
            return null;//ret;
        }

        /**
         * Parses an SC timestamp and returns a currentTimeMillis()-style timestamp
         * 
         * @see http://en.wikipedia.org/wiki/GSM_03.40#Time_Format
         */
        long getSCTimestampMillis() {
            // TP-Service-Centre-Time-Stamp
            int year = gsmBcdByteToInt(mPdu[mCur++]);
            int month = gsmBcdByteToInt(mPdu[mCur++]);
            int day = gsmBcdByteToInt(mPdu[mCur++]);
            int hour = gsmBcdByteToInt(mPdu[mCur++]);
            int minute = gsmBcdByteToInt(mPdu[mCur++]);
            int second = gsmBcdByteToInt(mPdu[mCur++]);

            // For the timezone, the most significant bit of the
            // least significant nibble is the sign byte
            // (meaning the max range of this field is 79 quarter-hours,
            // which is more than enough)

            byte tzByte = mPdu[mCur++];

            // Mask out sign bit.
            int timezoneOffset = gsmBcdByteToInt((byte) (tzByte & (~0x08)));

            timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;

            Time time = new Time(Time.TIMEZONE_UTC);

            // It's 2006.  Should I really support years < 2000?
            time.year = year >= 90 ? year + 1900 : year + 2000;
            time.month = month - 1;
            time.monthDay = day;
            time.hour = hour;
            time.minute = minute;
            time.second = second;

            // Timezone offset is in quarter hours.
            return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
        }

        /**
         * Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
         *
         * In GSM land, the least significant BCD digit is stored in the most
         * significant nibble.
         *
         * Out-of-range digits are treated as 0 for the sake of the time stamp,
         * because of this:
         *
         * TS 23.040 section 9.2.3.11
         * "if the MS receives a non-integer value in the SCTS, it shall
         * assume the digit is set to 0 but shall store the entire field
         * exactly as received"
         * 
         * @see Taken from com.android.internal.telephony.uicc.IccUtils
         */
        public static int gsmBcdByteToInt(byte b)
        {
            int ret = 0;

            // treat out-of-range BCD values as 0
            if((b & 0xf0) <= 0x90)
                ret = (b >> 4) & 0xf;
            if((b & 0x0f) <= 0x09)
                ret +=  (b & 0xf) * 10;
            return ret;
        }

        public boolean moreDataPresent()
        {
            return (mPdu.length > mCur);
        }

    }

}

一些免责声明:

  • 这仅适用于 GSM/3GPP 网络,不适用于 CDMA/3GPP;
  • 因为它是基于 Android 源代码的,所以上面的代码是 Apache License v2.0 许可的。
于 2015-05-27T13:56:41.507 回答
1

没有尝试过,但您可以查看该信息是否在传递给 BroadcastReceiver 的额外意图“pdus”中

@Override
public void onReceive(Context context, Intent intent) {

    Bundle bundle=intent.getExtras();

    Object[] messages=(Object[])bundle.get("pdus");
    SmsMessage[] sms=new SmsMessage[messages.length];

    for(int n=0;n<messages.length;n++){
        sms[n]=SmsMessage.createFromPdu((byte[]) messages[n]);
    }
}  

也许sms[0].getTimestampMillis()是你要找的。

于 2013-10-30T16:13:23.663 回答