我开发了一个发送 SMS 消息的应用程序,使用 BroadcastReceivers 成功发送和(未)传递消息。
在我的投递接收者中,我想知道消息被传递给目标收件人的时间。由于发送和接收设备都可以偶尔关闭,我认为将我收到交付广播的时间视为真正的交付时间是不正确的。
有没有办法在我的广播接收器中获得正确的交付时间?谢谢你。
我开发了一个发送 SMS 消息的应用程序,使用 BroadcastReceivers 成功发送和(未)传递消息。
在我的投递接收者中,我想知道消息被传递给目标收件人的时间。由于发送和接收设备都可以偶尔关闭,我认为将我收到交付广播的时间视为真正的交付时间是不正确的。
有没有办法在我的广播接收器中获得正确的交付时间?谢谢你。
这是一个有效的解决方案。
通过研究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);
}
}
}
一些免责声明:
没有尝试过,但您可以查看该信息是否在传递给 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()
是你要找的。