Android:获取短信发送时间

标签 android sms broadcastreceiver

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

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

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

最佳答案

这是一个可行的解决方案。

通过研究com.android.internal.telephony.gsm.SmsMessage的源代码类(这是处理在 GSM/3GPP 网络上解析 SMS PDU 的 Android 内部类)我发现 SMS-STATUS-REPORTS(=“发送报告”)包含第二个时间戳值,即“放电时间”。不幸的是,公共(public) 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 许可的。

关于Android:获取短信发送时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19681290/

相关文章:

android - 霍尼韦尔安卓条码扫描器

android - 在Android中获取图片路径

java - 如何使用 uriSMSURI 从收件箱中获取未读消息?

android - 如何在不以 Intent 启动 Activity 的情况下从在 list 中注册的广播接收器调用 Activity 中的方法?

android - 从 BroadcastReceiver 访问 R

java - (Android) 通过一个简单的套接字攻击服务器

sms - Twilio:此电话号码无法使用消息功能

php - 如何从服务器发送短信

android - 在android中更改位置时如何使用广播接收器跟踪触发服务?

java - 如何在回合制游戏中存储游戏棋子的路径?