Merge change 1127 into donut

* changes:
  SmsHeader rewrite, in preparation for migration to public API.
This commit is contained in:
Android (Google) Code Review
2009-05-19 19:52:07 -07:00
16 changed files with 661 additions and 698 deletions

View File

@ -20,6 +20,7 @@ import android.os.Parcel;
import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.EncodeException; import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
@ -307,7 +308,8 @@ public class SmsMessage {
if (PHONE_TYPE_CDMA == activePhone) { if (PHONE_TYPE_CDMA == activePhone) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested, header); destinationAddress, message, statusReportRequested,
SmsHeader.fromByteArray(header));
} else { } else {
spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested, header); destinationAddress, message, statusReportRequested, header);
@ -331,7 +333,7 @@ public class SmsMessage {
if (PHONE_TYPE_CDMA == activePhone) { if (PHONE_TYPE_CDMA == activePhone) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested); destinationAddress, message, statusReportRequested, null);
} else { } else {
spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested); destinationAddress, message, statusReportRequested);

View File

@ -21,6 +21,7 @@ import android.telephony.TelephonyManager;
import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.EncodeException; import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
@ -369,7 +370,8 @@ public class SmsMessage {
if (PHONE_TYPE_CDMA == activePhone) { if (PHONE_TYPE_CDMA == activePhone) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested, header); destinationAddress, message, statusReportRequested,
SmsHeader.fromByteArray(header));
} else { } else {
spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested, header); destinationAddress, message, statusReportRequested, header);
@ -395,7 +397,7 @@ public class SmsMessage {
if (PHONE_TYPE_CDMA == activePhone) { if (PHONE_TYPE_CDMA == activePhone) {
spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested); destinationAddress, message, statusReportRequested, null);
} else { } else {
spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
destinationAddress, message, statusReportRequested); destinationAddress, message, statusReportRequested);
@ -744,4 +746,3 @@ public class SmsMessage {
} }
} }
} }

View File

@ -182,7 +182,7 @@ public class GsmAlphabet {
return stringToGsm7BitPacked(data); return stringToGsm7BitPacked(data);
} }
int headerBits = header.length * 8; int headerBits = (header.length + 1) * 8;
int headerSeptets = headerBits / 7; int headerSeptets = headerBits / 7;
headerSeptets += (headerBits % 7) > 0 ? 1 : 0; headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
@ -194,7 +194,8 @@ public class GsmAlphabet {
(headerSeptets*7), true); (headerSeptets*7), true);
// Paste in the header // Paste in the header
System.arraycopy(header, 0, ret, 1, header.length); ret[1] = (byte)header.length;
System.arraycopy(header, 0, ret, 2, header.length);
return ret; return ret;
} }

View File

@ -122,7 +122,7 @@ public abstract class SMSDispatcher extends Handler {
* CONCATENATED_16_BIT_REFERENCE message set. Should be * CONCATENATED_16_BIT_REFERENCE message set. Should be
* incremented for each set of concatenated messages. * incremented for each set of concatenated messages.
*/ */
protected static int sConcatenatedRef; private static int sConcatenatedRef;
private SmsCounter mCounter; private SmsCounter mCounter;
@ -132,6 +132,11 @@ public abstract class SMSDispatcher extends Handler {
private static SmsMessageBase mSmsMessageBase; private static SmsMessageBase mSmsMessageBase;
private SmsMessageBase.SubmitPduBase mSubmitPduBase; private SmsMessageBase.SubmitPduBase mSubmitPduBase;
protected static int getNextConcatenatedRef() {
sConcatenatedRef += 1;
return sConcatenatedRef;
}
/** /**
* Implement the per-application based SMS control, which only allows * Implement the per-application based SMS control, which only allows
* a limit on the number of SMS/MMS messages an app can send in checking * a limit on the number of SMS/MMS messages an app can send in checking
@ -419,12 +424,15 @@ public abstract class SMSDispatcher extends Handler {
/** /**
* If this is the last part send the parts out to the application, otherwise * If this is the last part send the parts out to the application, otherwise
* the part is stored for later processing. * the part is stored for later processing.
*
* NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null.
*/ */
protected void processMessagePart(SmsMessageBase sms, int referenceNumber, protected void processMessagePart(SmsMessageBase sms,
int sequence, int count, int destinationPort) { SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
// Lookup all other related parts // Lookup all other related parts
StringBuilder where = new StringBuilder("reference_number ="); StringBuilder where = new StringBuilder("reference_number =");
where.append(referenceNumber); where.append(concatRef.refNumber);
where.append(" AND address = ?"); where.append(" AND address = ?");
String[] whereArgs = new String[] {sms.getOriginatingAddress()}; String[] whereArgs = new String[] {sms.getOriginatingAddress()};
@ -433,20 +441,19 @@ public abstract class SMSDispatcher extends Handler {
try { try {
cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null); cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
int cursorCount = cursor.getCount(); int cursorCount = cursor.getCount();
if (cursorCount != count - 1) { if (cursorCount != concatRef.msgCount - 1) {
// We don't have all the parts yet, store this one away // We don't have all the parts yet, store this one away
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("date", new Long(sms.getTimestampMillis())); values.put("date", new Long(sms.getTimestampMillis()));
values.put("pdu", HexDump.toHexString(sms.getPdu())); values.put("pdu", HexDump.toHexString(sms.getPdu()));
values.put("address", sms.getOriginatingAddress()); values.put("address", sms.getOriginatingAddress());
values.put("reference_number", referenceNumber); values.put("reference_number", concatRef.refNumber);
values.put("count", count); values.put("count", concatRef.msgCount);
values.put("sequence", sequence); values.put("sequence", concatRef.seqNumber);
if (destinationPort != -1) { if (portAddrs != null) {
values.put("destination_port", destinationPort); values.put("destination_port", portAddrs.destPort);
} }
mResolver.insert(mRawUri, values); mResolver.insert(mRawUri, values);
return; return;
} }
@ -454,7 +461,7 @@ public abstract class SMSDispatcher extends Handler {
int pduColumn = cursor.getColumnIndex("pdu"); int pduColumn = cursor.getColumnIndex("pdu");
int sequenceColumn = cursor.getColumnIndex("sequence"); int sequenceColumn = cursor.getColumnIndex("sequence");
pdus = new byte[count][]; pdus = new byte[concatRef.msgCount][];
for (int i = 0; i < cursorCount; i++) { for (int i = 0; i < cursorCount; i++) {
cursor.moveToNext(); cursor.moveToNext();
int cursorSequence = (int)cursor.getLong(sequenceColumn); int cursorSequence = (int)cursor.getLong(sequenceColumn);
@ -462,7 +469,7 @@ public abstract class SMSDispatcher extends Handler {
cursor.getString(pduColumn)); cursor.getString(pduColumn));
} }
// This one isn't in the DB, so add it // This one isn't in the DB, so add it
pdus[sequence - 1] = sms.getPdu(); pdus[concatRef.seqNumber - 1] = sms.getPdu();
// Remove the parts from the database // Remove the parts from the database
mResolver.delete(mRawUri, where.toString(), whereArgs); mResolver.delete(mRawUri, where.toString(), whereArgs);
@ -473,31 +480,34 @@ public abstract class SMSDispatcher extends Handler {
if (cursor != null) cursor.close(); if (cursor != null) cursor.close();
} }
/**
* TODO(cleanup): The following code has duplicated logic with
* the radio-specific dispatchMessage code, which is fragile,
* in addition to being redundant. Instead, if this method
* maybe returned the reassembled message (or just contents),
* the following code (which is not really related to
* reconstruction) could be better consolidated.
*/
// Dispatch the PDUs to applications // Dispatch the PDUs to applications
switch (destinationPort) { if (portAddrs != null) {
case SmsHeader.PORT_WAP_PUSH: { if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
// Build up the data stream // Build up the data stream
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
for (int i = 0; i < count; i++) { for (int i = 0; i < concatRef.msgCount; i++) {
SmsMessage msg = SmsMessage.createFromPdu(pdus[i]); SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
byte[] data = msg.getUserData(); byte[] data = msg.getUserData();
output.write(data, 0, data.length); output.write(data, 0, data.length);
}
// Handle the PUSH
mWapPush.dispatchWapPdu(output.toByteArray());
} else {
// The messages were sent to a port, so concoct a URI for it
dispatchPortAddressedPdus(pdus, portAddrs.destPort);
} }
} else {
// Handle the PUSH
mWapPush.dispatchWapPdu(output.toByteArray());
break;
}
case -1:
// The messages were not sent to a port // The messages were not sent to a port
dispatchPdus(pdus); dispatchPdus(pdus);
break;
default:
// The messages were sent to a port, so concoct a URI for it
dispatchPortAddressedPdus(pdus, destinationPort);
break;
} }
} }

View File

@ -16,227 +16,233 @@
package com.android.internal.telephony; package com.android.internal.telephony;
import android.telephony.SmsMessage;
import com.android.internal.util.HexDump; import com.android.internal.util.HexDump;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* This class represents a SMS user data header. * SMS user data header, as specified in TS 23.040 9.2.3.24.
*
*/ */
public class SmsHeader { public class SmsHeader {
/** See TS 23.040 9.2.3.24 for description of this element ID. */
public static final int CONCATENATED_8_BIT_REFERENCE = 0x00; // TODO(cleanup): this datastructure is generally referred to as
/** See TS 23.040 9.2.3.24 for description of this element ID. */ // the 'user data header' or UDH, and so the class name should
public static final int SPECIAL_SMS_MESSAGE_INDICATION = 0x01; // change to reflect this...
/** See TS 23.040 9.2.3.24 for description of this element ID. */
public static final int APPLICATION_PORT_ADDRESSING_8_BIT = 0x04; /** SMS user data header information element identifiers.
/** See TS 23.040 9.2.3.24 for description of this element ID. */ * (see TS 23.040 9.2.3.24)
public static final int APPLICATION_PORT_ADDRESSING_16_BIT= 0x05; */
/** See TS 23.040 9.2.3.24 for description of this element ID. */ public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00;
public static final int CONCATENATED_16_BIT_REFERENCE = 0x08; public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06;
public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07;
public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08;
public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09;
public static final int ELT_ID_TEXT_FORMATTING = 0x0A;
public static final int ELT_ID_PREDEFINED_SOUND = 0x0B;
public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C;
public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D;
public static final int ELT_ID_LARGE_ANIMATION = 0x0E;
public static final int ELT_ID_SMALL_ANIMATION = 0x0F;
public static final int ELT_ID_LARGE_PICTURE = 0x10;
public static final int ELT_ID_SMALL_PICTURE = 0x11;
public static final int ELT_ID_VARIABLE_PICTURE = 0x12;
public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13;
public static final int ELT_ID_EXTENDED_OBJECT = 0x14;
public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15;
public static final int ELT_ID_COMPRESSION_CONTROL = 0x16;
public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17;
public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18;
public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19;
public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A;
public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20;
public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21;
public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22;
public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
public static final int PORT_WAP_PUSH = 2948; public static final int PORT_WAP_PUSH = 2948;
public static final int PORT_WAP_WSP = 9200; public static final int PORT_WAP_WSP = 9200;
private byte[] m_data; public static class PortAddrs {
private ArrayList<Element> m_elements = new ArrayList<Element>(); public int destPort;
public int nbrOfHeaders; public int origPort;
public boolean areEightBits;
}
public static class ConcatRef {
public int refNumber;
public int seqNumber;
public int msgCount;
public boolean isEightBits;
}
/** /**
* Creates an SmsHeader object from raw user data header bytes. * A header element that is not explicitly parsed, meaning not
* * PortAddrs or ConcatRef.
* @param data is user data header bytes
* @return an SmsHeader object
*/ */
public static SmsHeader parse(byte[] data) { public static class MiscElt {
SmsHeader header = new SmsHeader(); public int id;
header.m_data = data; public byte[] data;
}
int index = 0; public PortAddrs portAddrs;
header.nbrOfHeaders = 0; public ConcatRef concatRef;
while (index < data.length) { public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
int id = data[index++] & 0xff;
int length = data[index++] & 0xff; public SmsHeader() {}
byte[] elementData = new byte[length];
System.arraycopy(data, index, elementData, 0, length); /**
header.add(new Element(id, elementData)); * Create structured SmsHeader object from serialized byte array representation.
index += length; * (see TS 23.040 9.2.3.24)
header.nbrOfHeaders++; * @param data is user data header bytes
* @return SmsHeader object
*/
public static SmsHeader fromByteArray(byte[] data) {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
SmsHeader smsHeader = new SmsHeader();
while (inStream.available() > 0) {
/**
* NOTE: as defined in the spec, ConcatRef and PortAddr
* fields should not reoccur, but if they do the last
* occurrence is to be used.
*/
int id = inStream.read();
int length = inStream.read();
ConcatRef concatRef;
PortAddrs portAddrs;
switch (id) {
case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
concatRef = new ConcatRef();
concatRef.refNumber = inStream.read();
concatRef.msgCount = inStream.read();
concatRef.seqNumber = inStream.read();
concatRef.isEightBits = true;
smsHeader.concatRef = concatRef;
break;
case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
concatRef = new ConcatRef();
concatRef.refNumber = (inStream.read() << 8) | inStream.read();
concatRef.msgCount = inStream.read();
concatRef.seqNumber = inStream.read();
concatRef.isEightBits = false;
smsHeader.concatRef = concatRef;
break;
case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
portAddrs = new PortAddrs();
portAddrs.destPort = inStream.read();
portAddrs.origPort = inStream.read();
portAddrs.areEightBits = true;
smsHeader.portAddrs = portAddrs;
break;
case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
portAddrs = new PortAddrs();
portAddrs.destPort = (inStream.read() << 8) | inStream.read();
portAddrs.origPort = (inStream.read() << 8) | inStream.read();
portAddrs.areEightBits = false;
smsHeader.portAddrs = portAddrs;
break;
default:
MiscElt miscElt = new MiscElt();
miscElt.id = id;
miscElt.data = new byte[length];
inStream.read(miscElt.data, 0, length);
smsHeader.miscEltList.add(miscElt);
}
}
return smsHeader;
}
/**
* Create serialized byte array representation from structured SmsHeader object.
* (see TS 23.040 9.2.3.24)
* @return Byte array representing the SmsHeader
*/
public static byte[] toByteArray(SmsHeader smsHeader) {
if ((smsHeader.portAddrs == null) &&
(smsHeader.concatRef == null) &&
(smsHeader.miscEltList.size() == 0)) {
return null;
} }
return header; ByteArrayOutputStream outStream = new ByteArrayOutputStream(SmsMessage.MAX_USER_DATA_BYTES);
} ConcatRef concatRef = smsHeader.concatRef;
if (concatRef != null) {
public SmsHeader() { } if (concatRef.isEightBits) {
outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
/** outStream.write(3);
* Returns the list of SmsHeader Elements that make up the header. outStream.write(concatRef.refNumber);
* } else {
* @return the list of SmsHeader Elements. outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
*/ outStream.write(4);
public ArrayList<Element> getElements() { outStream.write(concatRef.refNumber >>> 8);
return m_elements; outStream.write(concatRef.refNumber & 0x00FF);
} }
outStream.write(concatRef.msgCount);
/** outStream.write(concatRef.seqNumber);
* Add an element to the SmsHeader. }
* PortAddrs portAddrs = smsHeader.portAddrs;
* @param element to add. if (portAddrs != null) {
*/ if (portAddrs.areEightBits) {
public void add(Element element) { outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
m_elements.add(element); outStream.write(2);
outStream.write(portAddrs.destPort);
outStream.write(portAddrs.origPort);
} else {
outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
outStream.write(4);
outStream.write(portAddrs.destPort >>> 8);
outStream.write(portAddrs.destPort & 0x00FF);
outStream.write(portAddrs.origPort >>> 8);
outStream.write(portAddrs.origPort & 0x00FF);
}
}
for (MiscElt miscElt : smsHeader.miscEltList) {
outStream.write(miscElt.id);
outStream.write(miscElt.data.length);
outStream.write(miscElt.data, 0, miscElt.data.length);
}
return outStream.toByteArray();
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("UserDataHeader ");
builder.append("UDH LENGTH: " + m_data.length + " octets"); builder.append("{ ConcatRef ");
builder.append("UDH: "); if (concatRef == null) {
builder.append(HexDump.toHexString(m_data)); builder.append("unset");
builder.append("\n"); } else {
builder.append("{ refNumber=" + concatRef.refNumber);
for (Element e : getElements()) { builder.append(", msgCount=" + concatRef.msgCount);
builder.append(" 0x" + HexDump.toHexString((byte)e.getID()) + " - "); builder.append(", seqNumber=" + concatRef.seqNumber);
switch (e.getID()) { builder.append(", isEightBits=" + concatRef.isEightBits);
case CONCATENATED_8_BIT_REFERENCE: { builder.append(" }");
builder.append("Concatenated Short Message 8bit ref\n");
byte[] data = e.getData();
builder.append(" " + data.length + " (0x");
builder.append(HexDump.toHexString((byte)data.length)
+ ") Bytes - Information Element\n");
builder.append(" " + data[0] + " : SM reference number\n");
builder.append(" " + data[1] + " : number of messages\n");
builder.append(" " + data[2] + " : this SM sequence number\n");
break;
}
case CONCATENATED_16_BIT_REFERENCE: {
builder.append("Concatenated Short Message 16bit ref\n");
byte[] data = e.getData();
builder.append(" " + data.length + " (0x");
builder.append(HexDump.toHexString((byte)data.length)
+ ") Bytes - Information Element\n");
builder.append(" " + (data[0] & 0xff) * 256 + (data[1] & 0xff)
+ " : SM reference number\n");
builder.append(" " + data[2] + " : number of messages\n");
builder.append(" " + data[3] + " : this SM sequence number\n");
break;
}
case APPLICATION_PORT_ADDRESSING_8_BIT:
{
builder.append("Application port addressing 8bit\n");
byte[] data = e.getData();
builder.append(" " + data.length + " (0x");
builder.append(HexDump.toHexString(
(byte)data.length) + ") Bytes - Information Element\n");
int source = (data[0] & 0xff);
builder.append(" " + source + " : DESTINATION port\n");
int dest = (data[1] & 0xff);
builder.append(" " + dest + " : SOURCE port\n");
break;
}
case APPLICATION_PORT_ADDRESSING_16_BIT: {
builder.append("Application port addressing 16bit\n");
byte[] data = e.getData();
builder.append(" " + data.length + " (0x");
builder.append(HexDump.toHexString((byte)data.length)
+ ") Bytes - Information Element\n");
int source = (data[0] & 0xff) << 8;
source |= (data[1] & 0xff);
builder.append(" " + source + " : DESTINATION port\n");
int dest = (data[2] & 0xff) << 8;
dest |= (data[3] & 0xff);
builder.append(" " + dest + " : SOURCE port\n");
break;
}
default: {
builder.append("Unknown element\n");
break;
}
}
} }
builder.append(", PortAddrs ");
if (portAddrs == null) {
builder.append("unset");
} else {
builder.append("{ destPort=" + portAddrs.destPort);
builder.append(", origPort=" + portAddrs.origPort);
builder.append(", areEightBits=" + portAddrs.areEightBits);
builder.append(" }");
}
for (MiscElt miscElt : miscEltList) {
builder.append(", MiscElt ");
builder.append("{ id=" + miscElt.id);
builder.append(", length=" + miscElt.data.length);
builder.append(", data=" + HexDump.toHexString(miscElt.data));
builder.append(" }");
}
builder.append(" }");
return builder.toString(); return builder.toString();
} }
private int calcSize() {
int size = 1; // +1 for the UDHL field
for (Element e : m_elements) {
size += e.getData().length;
size += 2; // 1 byte ID, 1 byte length
}
return size;
}
/**
* Converts SmsHeader object to a byte array as specified in TS 23.040 9.2.3.24.
* @return Byte array representing the SmsHeader
*/
public byte[] toByteArray() {
if (m_elements.size() == 0) return null;
if (m_data == null) {
int size = calcSize();
int cur = 1;
m_data = new byte[size];
m_data[0] = (byte) (size-1); // UDHL does not include itself
for (Element e : m_elements) {
int length = e.getData().length;
m_data[cur++] = (byte) e.getID();
m_data[cur++] = (byte) length;
System.arraycopy(e.getData(), 0, m_data, cur, length);
cur += length;
}
}
return m_data;
}
/**
* A single Element in the SMS User Data Header.
*
* See TS 23.040 9.2.3.24.
*
*/
public static class Element {
private byte[] m_data;
private int m_id;
public Element(int id, byte[] data) {
m_id = id;
m_data = data;
}
/**
* Returns the Information Element Identifier for this element.
*
* @return the IE identifier.
*/
public int getID() {
return m_id;
}
/**
* Returns the data portion of this element.
*
* @return element data.
*/
public byte[] getData() {
return m_data;
}
}
} }

View File

@ -245,18 +245,21 @@ public abstract class SmsMessageBase {
/** /**
* Returns an object representing the user data header * Returns an object representing the user data header
* *
* @return an object representing the user data header
*
* {@hide} * {@hide}
*/ */
public SmsHeader getUserDataHeader() { public SmsHeader getUserDataHeader() {
return userDataHeader; return userDataHeader;
} }
/**
* TODO(cleanup): The term PDU is used in a seemingly non-unique
* manner -- for example, what is the difference between this byte
* array and the contents of SubmitPdu objects. Maybe a more
* illustrative term would be appropriate.
*/
/** /**
* Returns the raw PDU for the message. * Returns the raw PDU for the message.
*
* @return the raw PDU for the message.
*/ */
public byte[] getPdu() { public byte[] getPdu() {
return mPdu; return mPdu;

View File

@ -29,7 +29,6 @@ import android.util.Log;
import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SMSDispatcher;
//import com.android.internal.telephony.SMSDispatcher.SmsTracker;
import com.android.internal.telephony.cdma.SmsMessage; import com.android.internal.telephony.cdma.SmsMessage;
import com.android.internal.telephony.cdma.sms.SmsEnvelope; import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.util.HexDump; import com.android.internal.util.HexDump;
@ -42,8 +41,11 @@ import java.util.HashMap;
final class CdmaSMSDispatcher extends SMSDispatcher { final class CdmaSMSDispatcher extends SMSDispatcher {
private static final String TAG = "CDMA"; private static final String TAG = "CDMA";
private CDMAPhone mCdmaPhone;
CdmaSMSDispatcher(CDMAPhone phone) { CdmaSMSDispatcher(CDMAPhone phone) {
super(phone); super(phone);
mCdmaPhone = phone;
} }
/** /**
@ -70,50 +72,39 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
if (smsb == null) { if (smsb == null) {
return; return;
} }
SmsMessage sms = (SmsMessage) smsb;
int teleService;
boolean handled = false;
// Decode BD stream and set sms variables. // Decode BD stream and set sms variables.
SmsMessage sms = (SmsMessage) smsb;
sms.parseSms(); sms.parseSms();
teleService = sms.getTeleService(); int teleService = sms.getTeleService();
boolean handled = false;
// Teleservices W(E)MT and VMN are handled together: // Teleservices W(E)MT and VMN are handled together:
if ((SmsEnvelope.TELESERVICE_WMT == teleService) if ((teleService == SmsEnvelope.TELESERVICE_WMT)
||(SmsEnvelope.TELESERVICE_WEMT == teleService) || (teleService == SmsEnvelope.TELESERVICE_WEMT)
||(SmsEnvelope.TELESERVICE_VMN == teleService)){ || (teleService == SmsEnvelope.TELESERVICE_VMN)) {
// From here on we need decoded BD. // From here on we need decoded BD.
// Special case the message waiting indicator messages // Special case the message waiting indicator messages
if (sms.isMWISetMessage()) { if (sms.isMWISetMessage()) {
((CDMAPhone) mPhone).updateMessageWaitingIndicator(true); mCdmaPhone.updateMessageWaitingIndicator(true);
handled |= sms.isMwiDontStore();
if (sms.isMwiDontStore()) {
handled = true;
}
if (Config.LOGD) { if (Config.LOGD) {
Log.d(TAG, Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
"Received voice mail indicator set SMS shouldStore=" + !handled);
} }
} else if (sms.isMWIClearMessage()) { } else if (sms.isMWIClearMessage()) {
((CDMAPhone) mPhone).updateMessageWaitingIndicator(false); mCdmaPhone.updateMessageWaitingIndicator(false);
handled |= sms.isMwiDontStore();
if (sms.isMwiDontStore()) {
handled = true;
}
if (Config.LOGD) { if (Config.LOGD) {
Log.d(TAG, Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
"Received voice mail indicator clear SMS shouldStore=" + !handled);
} }
} }
} }
if (null == sms.getUserData()){ if (sms.getUserData() == null) {
handled = true;
if (Config.LOGD) { if (Config.LOGD) {
Log.d(TAG, "Received SMS without user data"); Log.d(TAG, "Received SMS without user data");
} }
handled = true;
} }
if (handled) return; if (handled) return;
@ -123,82 +114,44 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
return; return;
} }
// Parse the headers to see if this is partial, or port addressed /**
int referenceNumber = -1; * TODO(cleanup): Why are we using a getter method for this
int count = 0; * (and for so many other sms fields)? Trivial getters and
int sequence = 0; * setters like this are direct violations of the style guide.
int destPort = -1; * If the purpose is to protect agaist writes (by not
// From here on we need BD distributed to SMS member variables. * providing a setter) then any protection is illusory (and
* hence bad) for cases where the values are not primitives,
* such as this call for the header. Since this is an issue
* with the public API it cannot be changed easily, but maybe
* something can be done eventually.
*/
SmsHeader smsHeader = sms.getUserDataHeader();
SmsHeader header = sms.getUserDataHeader(); /**
if (header != null) { * TODO(cleanup): Since both CDMA and GSM use the same header
for (SmsHeader.Element element : header.getElements()) { * format, this dispatch processing is naturally identical,
try { * and code should probably not be replicated explicitly.
switch (element.getID()) { */
case SmsHeader.CONCATENATED_8_BIT_REFERENCE: { // See if message is partial or port addressed.
byte[] data = element.getData(); if ((smsHeader == null) || (smsHeader.concatRef == null)) {
// Message is not partial (not part of concatenated sequence).
referenceNumber = data[0] & 0xff;
count = data[1] & 0xff;
sequence = data[2] & 0xff;
// Per TS 23.040, 9.2.3.24.1: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.CONCATENATED_16_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = (data[0] & 0xff) * 256 + (data[1] & 0xff);
count = data[2] & 0xff;
sequence = data[3] & 0xff;
// Per TS 23.040, 9.2.3.24.8: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT: {
byte[] data = element.getData();
destPort = (data[0] & 0xff) << 8;
destPort |= (data[1] & 0xff);
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
Log.e(TAG, "Bad element in header", e);
return; // TODO: NACK the message or something, don't just discard.
}
}
}
if (referenceNumber == -1) {
// notify everyone of the message if it isn't partial
byte[][] pdus = new byte[1][]; byte[][] pdus = new byte[1][];
pdus[0] = sms.getPdu(); pdus[0] = sms.getPdu();
if (destPort != -1) {// GSM-style WAP indication if (smsHeader.portAddrs != null) {
if (destPort == SmsHeader.PORT_WAP_PUSH) { if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
// GSM-style WAP indication
mWapPush.dispatchWapPdu(sms.getUserData()); mWapPush.dispatchWapPdu(sms.getUserData());
} }
// The message was sent to a port, so concoct a URI for it // The message was sent to a port, so concoct a URI for it.
dispatchPortAddressedPdus(pdus, destPort); dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
} else { } else {
// It's a normal message, dispatch it // Normal short and non-port-addressed message, dispatch it.
dispatchPdus(pdus); dispatchPdus(pdus);
} }
} else { } else {
// Process the message part // Process the message part.
processMessagePart(sms, referenceNumber, sequence, count, destPort); processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
} }
} }
@ -314,41 +267,49 @@ final class CdmaSMSDispatcher extends SMSDispatcher {
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
protected void sendMultipartText(String destinationAddress, String scAddress, protected void sendMultipartText(String destAddr, String scAddr,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) { ArrayList<PendingIntent> deliveryIntents) {
int ref = ++sConcatenatedRef & 0xff; /**
* TODO(cleanup): There is no real code difference between
* this and the GSM version, and hence it should be moved to
* the base class or consolidated somehow, provided calling
* the proper submitpdu stuff can be arranged.
*/
for (int i = 0, count = parts.size(); i < count; i++) { int refNumber = getNextConcatenatedRef() & 0x00FF;
// build SmsHeader data
byte[] data = new byte[5]; for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
data[0] = (byte) SmsHeader.CONCATENATED_8_BIT_REFERENCE; SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
data[1] = (byte) 3; // 3 bytes follow concatRef.refNumber = refNumber;
data[2] = (byte) ref; // reference #, unique per message concatRef.seqNumber = i + 1; // 1-based sequence
data[3] = (byte) count; // total part count concatRef.msgCount = msgCount;
data[4] = (byte) (i + 1); // 1-based sequence concatRef.isEightBits = true;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
PendingIntent sentIntent = null; PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
if (sentIntents != null && sentIntents.size() > i) { if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i); sentIntent = sentIntents.get(i);
} }
PendingIntent deliveryIntent = null;
if (deliveryIntents != null && deliveryIntents.size() > i) { if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i); deliveryIntent = deliveryIntents.get(i);
} }
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(scAddr, destAddr,
parts.get(i), deliveryIntent != null, data); parts.get(i), deliveryIntent != null, smsHeader);
sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent); sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
} }
} }
protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, protected void sendSubmitPdu(SmsMessage.SubmitPdu submitPdu, PendingIntent sentIntent,
PendingIntent deliveryIntent) { PendingIntent deliveryIntent) {
super.sendRawPdu(smsc, pdu, sentIntent, deliveryIntent); sendRawPdu(submitPdu.encodedScAddress, submitPdu.encodedMessage,
sentIntent, deliveryIntent);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */

View File

@ -276,6 +276,15 @@ public class SmsMessage extends SmsMessageBase {
return 0; return 0;
} }
/**
* TODO(cleanup): why do getSubmitPdu methods take an scAddr input
* and do nothing with it? GSM allows us to specify a SC (eg,
* when responding to an SMS that explicitly requests the response
* is sent to a specific SC), or pass null to use the default
* value. Is there no similar notion in CDMA? Or do we just not
* have it hooked up?
*/
/** /**
* Get an SMS-SUBMIT PDU for a destination address and a message * Get an SMS-SUBMIT PDU for a destination address and a message
* *
@ -290,88 +299,53 @@ public class SmsMessage extends SmsMessageBase {
* Returns null on encode error. * Returns null on encode error.
* @hide * @hide
*/ */
public static SubmitPdu getSubmitPdu(String scAddr, public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader) {
boolean statusReportRequested, byte[] headerData) {
/**
* TODO(cleanup): why does this method take an scAddr input
* and do nothing with it? GSM allows us to specify a SC (eg,
* when responding to an SMS that explicitly requests the
* response is sent to a specific SC), or pass null to use the
* default value. Is there no similar notion in CDMA? Or do
* we just not have it hooked up?
*/
/**
* TODO(cleanup): Do we really want silent failure like this?
* Would it not be much more reasonable to make sure we don't
* call this function if we really want nothing done?
*/
if (message == null || destAddr == null) { if (message == null || destAddr == null) {
return null; return null;
} }
UserData uData = new UserData(); UserData uData = new UserData();
uData.payloadStr = message; uData.payloadStr = message;
if(headerData != null) { uData.userDataHeader = smsHeader;
/** return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
* TODO(cleanup): we force the outside to deal with _all_
* of the raw details of properly constructing serialized
* headers, unserialze here, and then promptly reserialze
* during encoding -- rather undesirable.
*/
uData.userDataHeader = SmsHeader.parse(headerData);
}
return privateGetSubmitPdu(destAddr, statusReportRequested, uData, (headerData == null));
}
/**
* Get an SMS-SUBMIT PDU for a destination address and a message
*
* @param scAddress Service Centre address. Null means use default.
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
*/
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message,
boolean statusReportRequested) {
return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
} }
/** /**
* Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
* *
* @param scAddress Service Centre address. null == use default * @param scAddr Service Centre address. null == use default
* @param destinationAddress the address of the destination for the message * @param destAddr the address of the destination for the message
* @param destinationPort the port to deliver the message to at the * @param destPort the port to deliver the message to at the
* destination * destination
* @param data the data for the message * @param data the data for the message
* @return a <code>SubmitPdu</code> containing the encoded SC * @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message. * address, if applicable, and the encoded message.
* Returns null on encode error. * Returns null on encode error.
*/ */
public static SubmitPdu getSubmitPdu(String scAddress, public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, short destPort,
String destAddr, short destinationPort, byte[] data, byte[] data, boolean statusReportRequested) {
boolean statusReportRequested) {
/** /**
* TODO(cleanup): if we had properly exposed SmsHeader * TODO(cleanup): this is not a general-purpose SMS creation
* information, this mess of many getSubmitPdu public * method, but rather something specialized to messages
* interface methods that currently pollute the api could have * containing OCTET encoded (meaning non-human-readable) user
* been much more cleanly collapsed into one. * data. The name should reflect that, and not just overload.
*/ */
/** SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
* TODO(cleanup): header serialization should be put somewhere portAddrs.destPort = destPort;
* canonical to allow proper debugging and reuse. portAddrs.origPort = 0;
*/ portAddrs.areEightBits = false;
byte[] destPort = new byte[4];
destPort[0] = (byte) ((destinationPort >> 8) & 0xFF); // MSB of destination port
destPort[1] = (byte) (destinationPort & 0xFF); // LSB of destination port
destPort[2] = 0x00; // MSB of originating port
destPort[3] = 0x00; // LSB of originating port
SmsHeader smsHeader = new SmsHeader(); SmsHeader smsHeader = new SmsHeader();
smsHeader.add( smsHeader.portAddrs = portAddrs;
new SmsHeader.Element(SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT, destPort));
smsHeader.nbrOfHeaders = smsHeader.getElements().size();
UserData uData = new UserData(); UserData uData = new UserData();
uData.userDataHeader = smsHeader; uData.userDataHeader = smsHeader;
@ -379,7 +353,7 @@ public class SmsMessage extends SmsMessageBase {
uData.msgEncodingSet = true; uData.msgEncodingSet = true;
uData.payload = data; uData.payload = data;
return privateGetSubmitPdu(destAddr, statusReportRequested, uData, true); return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
} }
static class PduParser { static class PduParser {
@ -445,31 +419,23 @@ public class SmsMessage extends SmsMessageBase {
* {@inheritDoc} * {@inheritDoc}
*/ */
public boolean isMWIClearMessage() { public boolean isMWIClearMessage() {
if ((mBearerData != null) && (0 == mBearerData.numberOfMessages)) { return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
return true;
}
return false;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public boolean isMWISetMessage() { public boolean isMWISetMessage() {
if ((mBearerData != null) && (mBearerData.numberOfMessages >0)) { return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
return true;
}
return false;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public boolean isMwiDontStore() { public boolean isMwiDontStore() {
if ((mBearerData != null) && (mBearerData.numberOfMessages >0) return ((mBearerData != null) &&
&& (null == mBearerData.userData)) { (mBearerData.numberOfMessages > 0) &&
return true; (mBearerData.userData == null));
}
return false;
} }
/** /**
@ -478,7 +444,7 @@ public class SmsMessage extends SmsMessageBase {
* shifted to the bits 31-16. * shifted to the bits 31-16.
*/ */
public int getStatus() { public int getStatus() {
return(status<<16); return (status << 16);
} }
/** /**
@ -518,7 +484,7 @@ public class SmsMessage extends SmsMessageBase {
*/ */
private void parsePdu(byte[] pdu) { private void parsePdu(byte[] pdu) {
ByteArrayInputStream bais = new ByteArrayInputStream(pdu); ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
DataInputStream dis = new DataInputStream(new BufferedInputStream(bais)); DataInputStream dis = new DataInputStream(bais);
byte length; byte length;
int bearerDataLength; int bearerDataLength;
SmsEnvelope env = new SmsEnvelope(); SmsEnvelope env = new SmsEnvelope();
@ -568,38 +534,23 @@ public class SmsMessage extends SmsMessageBase {
protected void parseSms() { protected void parseSms() {
mBearerData = BearerData.decode(mEnvelope.bearerData); mBearerData = BearerData.decode(mEnvelope.bearerData);
messageRef = mBearerData.messageId; messageRef = mBearerData.messageId;
if (mBearerData.userData != null) {
userData = mBearerData.userData.payload;
userDataHeader = mBearerData.userData.userDataHeader;
messageBody = mBearerData.userData.payloadStr;
}
// TP-Message-Type-Indicator // TP-Message-Type-Indicator (See 3GPP2 C.S0015-B, v2, 4.5.1)
// (See 3GPP2 C.S0015-B, v2, 4.5.1) switch (mBearerData.messageType) {
int messageType = mBearerData.messageType;
switch (messageType) {
case BearerData.MESSAGE_TYPE_USER_ACK: case BearerData.MESSAGE_TYPE_USER_ACK:
case BearerData.MESSAGE_TYPE_READ_ACK: case BearerData.MESSAGE_TYPE_READ_ACK:
case BearerData.MESSAGE_TYPE_DELIVER: case BearerData.MESSAGE_TYPE_DELIVER:
// Deliver (mobile-terminated only)
parseSmsDeliver();
break;
case BearerData.MESSAGE_TYPE_DELIVERY_ACK: case BearerData.MESSAGE_TYPE_DELIVERY_ACK:
parseSmsDeliveryAck();
break; break;
default: default:
// the rest of these throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
throw new RuntimeException("Unsupported message type: " + messageType);
} }
}
/**
* TODO(cleanup): why are there two nearly identical functions
* below? More rubbish...
*/
/**
* Parses a SMS-DELIVER message. (mobile-terminated only)
* See 3GPP2 C.S0015-B, v2, 4.4.1
*/
private void parseSmsDeliver() {
if (originatingAddress != null) { if (originatingAddress != null) {
originatingAddress.address = new String(originatingAddress.origBytes); originatingAddress.address = new String(originatingAddress.origBytes);
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: " if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
@ -612,46 +563,13 @@ public class SmsMessage extends SmsMessageBase {
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis); if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
parseUserData(mBearerData.userData); // TODO(Teleca): do we really want this test to occur only for DELIVERY_ACKs?
} if ((mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) &&
(mBearerData.errorClass != BearerData.ERROR_UNDEFINED)) {
/**
* Parses a SMS-DELIVER message. (mobile-terminated only)
* See 3GPP2 C.S0015-B, v2, 4.4.1
*/
private void parseSmsDeliveryAck() {
if (originatingAddress != null) {
originatingAddress.address = new String(originatingAddress.origBytes);
if (Config.LOGV) Log.v(LOG_TAG, "SMS originating address: "
+ originatingAddress.address);
}
if (mBearerData.timeStamp != null) {
scTimeMillis = PduParser.getSCTimestampMillis(mBearerData.timeStamp);
}
if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
if (mBearerData.errorClass != BearerData.ERROR_UNDEFINED) {
status = mBearerData.errorClass << 8; status = mBearerData.errorClass << 8;
status |= mBearerData.messageStatus; status |= mBearerData.messageStatus;
} }
parseUserData(mBearerData.userData);
}
/**
* Copy parsed user data out from internal datastructures.
*/
private void parseUserData(UserData uData) {
if (uData == null) {
return;
}
userData = uData.payload;
userDataHeader = uData.userDataHeader;
messageBody = uData.payloadStr;
if (messageBody != null) { if (messageBody != null) {
if (Config.LOGV) Log.v(LOG_TAG, "SMS message body: '" + messageBody + "'"); if (Config.LOGV) Log.v(LOG_TAG, "SMS message body: '" + messageBody + "'");
parseMessageBody(); parseMessageBody();
@ -708,7 +626,7 @@ public class SmsMessage extends SmsMessageBase {
* @return byte stream for SubmitPdu. * @return byte stream for SubmitPdu.
*/ */
private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
UserData userData, boolean useNewId) { UserData userData) {
/** /**
* TODO(cleanup): give this function a more meaningful name. * TODO(cleanup): give this function a more meaningful name.
@ -720,7 +638,7 @@ public class SmsMessage extends SmsMessageBase {
BearerData bearerData = new BearerData(); BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT; bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
if (useNewId) setNextMessageId(); if (userData != null) setNextMessageId();
bearerData.messageId = nextMessageId; bearerData.messageId = nextMessageId;
bearerData.deliveryAckReq = statusReportRequested; bearerData.deliveryAckReq = statusReportRequested;
@ -812,6 +730,15 @@ public class SmsMessage extends SmsMessageBase {
dos.write(env.bearerData, 0, env.bearerData.length); dos.write(env.bearerData, 0, env.bearerData.length);
dos.close(); dos.close();
/**
* TODO(cleanup) -- This is the only place where mPdu is
* defined, and this is not obviously the only place where
* it needs to be defined. It would be much nicer if
* accessing the serialized representation used a less
* fragile mechanism. Maybe the getPdu method could
* generate a representation if there was not yet one?
*/
mPdu = baos.toByteArray(); mPdu = baos.toByteArray();
} catch (IOException ex) { } catch (IOException ex) {
Log.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex); Log.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);

View File

@ -189,8 +189,12 @@ public final class BearerData{
public int messageStatus = STATUS_UNDEFINED; public int messageStatus = STATUS_UNDEFINED;
/** /**
* 1-bit value that indicates whether a User Data Header is present. * 1-bit value that indicates whether a User Data Header (UDH) is present.
* (See 3GPP2 C.S0015-B, v2, 4.5.1) * (See 3GPP2 C.S0015-B, v2, 4.5.1)
*
* NOTE: during encoding, this value will be set based on the
* presence of a UDH in the structured data, any existing setting
* will be overwritten.
*/ */
public boolean hasUserDataHeader; public boolean hasUserDataHeader;
@ -248,25 +252,27 @@ public final class BearerData{
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("BearerData:\n"); builder.append("BearerData ");
builder.append(" messageType: " + messageType + "\n"); builder.append("{ messageType=" + messageType);
builder.append(" messageId: " + (int)messageId + "\n"); builder.append(", messageId=" + (int)messageId);
builder.append(" priority: " + (priorityIndicatorSet ? priority : "not set") + "\n"); builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
builder.append(" privacy: " + (privacyIndicatorSet ? privacy : "not set") + "\n"); builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
builder.append(" alert: " + (alertIndicatorSet ? alert : "not set") + "\n"); builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
builder.append(" displayMode: " + (displayModeSet ? displayMode : "not set") + "\n"); builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
builder.append(" language: " + (languageIndicatorSet ? language : "not set") + "\n"); builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
builder.append(" errorClass: " + (messageStatusSet ? errorClass : "not set") + "\n"); builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
builder.append(" msgStatus: " + (messageStatusSet ? messageStatus : "not set") + "\n"); builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
builder.append(" hasUserDataHeader: " + hasUserDataHeader + "\n"); builder.append(", timeStamp=" +
builder.append(" timeStamp: " + timeStamp + "\n"); ((timeStamp != null) ? HexDump.toHexString(timeStamp) : "unset"));
builder.append(" userAckReq: " + userAckReq + "\n"); builder.append(", userAckReq=" + userAckReq);
builder.append(" deliveryAckReq: " + deliveryAckReq + "\n"); builder.append(", deliveryAckReq=" + deliveryAckReq);
builder.append(" readAckReq: " + readAckReq + "\n"); builder.append(", readAckReq=" + readAckReq);
builder.append(" reportReq: " + reportReq + "\n"); builder.append(", reportReq=" + reportReq);
builder.append(" numberOfMessages: " + numberOfMessages + "\n"); builder.append(", numberOfMessages=" + numberOfMessages);
builder.append(" callbackNumber: " + callbackNumber + "\n"); builder.append(", callbackNumber=" + callbackNumber);
builder.append(" userData: " + userData + "\n"); builder.append(", hasUserDataHeader=" + hasUserDataHeader);
builder.append(", userData=" + userData);
builder.append(" }");
return builder.toString(); return builder.toString();
} }
@ -335,12 +341,19 @@ public final class BearerData{
private static void encodeUserDataPayload(UserData uData) private static void encodeUserDataPayload(UserData uData)
throws CodingException throws CodingException
{ {
byte[] headerData = null;
if (uData.userDataHeader != null) headerData = SmsHeader.toByteArray(uData.userDataHeader);
int headerDataLen = (headerData == null) ? 0 : headerData.length + 1; // + length octet
byte[] payloadData;
if (uData.msgEncodingSet) { if (uData.msgEncodingSet) {
if (uData.msgEncoding == UserData.ENCODING_OCTET) { if (uData.msgEncoding == UserData.ENCODING_OCTET) {
if (uData.payload == null) { if (uData.payload == null) {
Log.e(LOG_TAG, "user data with octet encoding but null payload"); Log.e(LOG_TAG, "user data with octet encoding but null payload");
// TODO(code_review): reasonable for fail case? or maybe bail on encoding? // TODO(code_review): reasonable for fail case? or maybe bail on encoding?
uData.payload = new byte[0]; payloadData = new byte[0];
} else {
payloadData = uData.payload;
} }
} else { } else {
if (uData.payloadStr == null) { if (uData.payloadStr == null) {
@ -349,11 +362,11 @@ public final class BearerData{
uData.payloadStr = ""; uData.payloadStr = "";
} }
if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
uData.payload = encode7bitGsm(uData.payloadStr); payloadData = encode7bitGsm(uData.payloadStr);
} else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) { } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
uData.payload = encode7bitAscii(uData.payloadStr); payloadData = encode7bitAscii(uData.payloadStr);
} else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
uData.payload = encodeUtf16(uData.payloadStr); payloadData = encodeUtf16(uData.payloadStr);
} else { } else {
throw new CodingException("unsupported user data encoding (" + throw new CodingException("unsupported user data encoding (" +
uData.msgEncoding + ")"); uData.msgEncoding + ")");
@ -367,19 +380,28 @@ public final class BearerData{
uData.payloadStr = ""; uData.payloadStr = "";
} }
try { try {
uData.payload = encode7bitAscii(uData.payloadStr); payloadData = encode7bitAscii(uData.payloadStr);
uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
} catch (CodingException ex) { } catch (CodingException ex) {
uData.payload = encodeUtf16(uData.payloadStr); payloadData = encodeUtf16(uData.payloadStr);
uData.msgEncoding = UserData.ENCODING_UNICODE_16; uData.msgEncoding = UserData.ENCODING_UNICODE_16;
} }
uData.msgEncodingSet = true; uData.msgEncodingSet = true;
uData.numFields = uData.payloadStr.length(); uData.numFields = uData.payloadStr.length();
} }
if (uData.payload.length > SmsMessage.MAX_USER_DATA_BYTES) {
throw new CodingException("encoded user data too large (" + uData.payload.length + int totalLength = payloadData.length + headerDataLen;
if (totalLength > SmsMessage.MAX_USER_DATA_BYTES) {
throw new CodingException("encoded user data too large (" + totalLength +
" > " + SmsMessage.MAX_USER_DATA_BYTES + " bytes)"); " > " + SmsMessage.MAX_USER_DATA_BYTES + " bytes)");
} }
uData.payload = new byte[totalLength];
if (headerData != null) {
uData.payload[0] = (byte)headerData.length;
System.arraycopy(headerData, 0, uData.payload, 1, headerData.length);
}
System.arraycopy(payloadData, 0, uData.payload, headerDataLen, payloadData.length);
} }
private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream) private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
@ -394,11 +416,6 @@ public final class BearerData{
* *
*/ */
int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits; int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
byte[] headerData = null;
if (bData.hasUserDataHeader) {
headerData = bData.userData.userDataHeader.toByteArray();
dataBits += headerData.length * 8;
}
int paramBits = dataBits + 13; int paramBits = dataBits + 13;
if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
(bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
@ -413,7 +430,6 @@ public final class BearerData{
outStream.write(8, bData.userData.msgType); outStream.write(8, bData.userData.msgType);
} }
outStream.write(8, bData.userData.numFields); outStream.write(8, bData.userData.numFields);
if (headerData != null) outStream.writeByteArray(headerData.length * 8, headerData);
outStream.writeByteArray(dataBits, bData.userData.payload); outStream.writeByteArray(dataBits, bData.userData.payload);
if (paddingBits > 0) outStream.write(paddingBits, 0); if (paddingBits > 0) outStream.write(paddingBits, 0);
} }
@ -557,6 +573,8 @@ public final class BearerData{
* @return data byta array of raw encoded SMS bearer data. * @return data byta array of raw encoded SMS bearer data.
*/ */
public static byte[] encode(BearerData bData) { public static byte[] encode(BearerData bData) {
bData.hasUserDataHeader = ((bData.userData != null) &&
(bData.userData.userDataHeader != null));
try { try {
BitwiseOutputStream outStream = new BitwiseOutputStream(200); BitwiseOutputStream outStream = new BitwiseOutputStream(200);
outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER); outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
@ -723,11 +741,11 @@ public final class BearerData{
{ {
int offset = 0; int offset = 0;
if (hasUserDataHeader) { if (hasUserDataHeader) {
int udhLen = userData.payload[0]; int udhLen = userData.payload[0] & 0x00FF;
offset += udhLen; offset += udhLen + 1;
byte[] headerData = new byte[udhLen]; byte[] headerData = new byte[udhLen];
System.arraycopy(userData.payload, 1, headerData, 0, udhLen); System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
userData.userDataHeader = SmsHeader.parse(headerData); userData.userDataHeader = SmsHeader.fromByteArray(headerData);
} }
switch (userData.msgEncoding) { switch (userData.msgEncoding) {
case UserData.ENCODING_OCTET: case UserData.ENCODING_OCTET:

View File

@ -93,14 +93,15 @@ public class UserData {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("UserData:\n"); builder.append("UserData ");
builder.append(" msgEncoding: " + (msgEncodingSet ? msgEncoding : "not set") + "\n"); builder.append("{ msgEncoding=" + (msgEncodingSet ? msgEncoding : "unset"));
builder.append(" msgType: " + msgType + "\n"); builder.append(", msgType=" + msgType);
builder.append(" paddingBits: " + paddingBits + "\n"); builder.append(", paddingBits=" + paddingBits);
builder.append(" numFields: " + (int)numFields + "\n"); builder.append(", numFields=" + (int)numFields);
builder.append(" userDataHeader: " + userDataHeader + "\n"); builder.append(", userDataHeader=" + userDataHeader);
builder.append(" payload: '" + HexDump.toHexString(payload) + "'"); builder.append(", payload='" + HexDump.toHexString(payload) + "'");
builder.append(", payloadStr: '" + payloadStr + "'"); builder.append(", payloadStr='" + payloadStr + "'");
builder.append(" }");
return builder.toString(); return builder.toString();
} }

View File

@ -39,8 +39,11 @@ import java.util.HashMap;
final class GsmSMSDispatcher extends SMSDispatcher { final class GsmSMSDispatcher extends SMSDispatcher {
private static final String TAG = "GSM"; private static final String TAG = "GSM";
private GSMPhone mGsmPhone;
GsmSMSDispatcher(GSMPhone phone) { GsmSMSDispatcher(GSMPhone phone) {
super(phone); super(phone);
mGsmPhone = phone;
} }
/** /**
@ -97,110 +100,41 @@ final class GsmSMSDispatcher extends SMSDispatcher {
// Special case the message waiting indicator messages // Special case the message waiting indicator messages
if (sms.isMWISetMessage()) { if (sms.isMWISetMessage()) {
((GSMPhone) mPhone).updateMessageWaitingIndicator(true); mGsmPhone.updateMessageWaitingIndicator(true);
handled |= sms.isMwiDontStore();
if (sms.isMwiDontStore()) {
handled = true;
}
if (Config.LOGD) { if (Config.LOGD) {
Log.d(TAG, Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
"Received voice mail indicator set SMS shouldStore="
+ !handled);
} }
} else if (sms.isMWIClearMessage()) { } else if (sms.isMWIClearMessage()) {
((GSMPhone) mPhone).updateMessageWaitingIndicator(false); mGsmPhone.updateMessageWaitingIndicator(false);
handled |= sms.isMwiDontStore();
if (sms.isMwiDontStore()) {
handled = true;
}
if (Config.LOGD) { if (Config.LOGD) {
Log.d(TAG, Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
"Received voice mail indicator clear SMS shouldStore="
+ !handled);
} }
} }
if (handled) { if (handled) return;
return;
}
// Parse the headers to see if this is partial, or port addressed SmsHeader smsHeader = sms.getUserDataHeader();
int referenceNumber = -1; // See if message is partial or port addressed.
int count = 0; if ((smsHeader == null) || (smsHeader.concatRef == null)) {
int sequence = 0; // Message is not partial (not part of concatenated sequence).
int destPort = -1;
SmsHeader header = sms.getUserDataHeader();
if (header != null) {
for (SmsHeader.Element element : header.getElements()) {
try {
switch (element.getID()) {
case SmsHeader.CONCATENATED_8_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = data[0] & 0xff;
count = data[1] & 0xff;
sequence = data[2] & 0xff;
// Per TS 23.040, 9.2.3.24.1: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.CONCATENATED_16_BIT_REFERENCE: {
byte[] data = element.getData();
referenceNumber = (data[0] & 0xff) * 256 + (data[1] & 0xff);
count = data[2] & 0xff;
sequence = data[3] & 0xff;
// Per TS 23.040, 9.2.3.24.8: If the count is zero, sequence
// is zero, or sequence > count, ignore the entire element
if (count == 0 || sequence == 0 || sequence > count) {
referenceNumber = -1;
}
break;
}
case SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT: {
byte[] data = element.getData();
destPort = (data[0] & 0xff) << 8;
destPort |= (data[1] & 0xff);
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
Log.e(TAG, "Bad element in header", e);
return; // TODO: NACK the message or something, don't just discard.
}
}
}
if (referenceNumber == -1) {
// notify everyone of the message if it isn't partial
byte[][] pdus = new byte[1][]; byte[][] pdus = new byte[1][];
pdus[0] = sms.getPdu(); pdus[0] = sms.getPdu();
if (destPort != -1) { if (smsHeader.portAddrs != null) {
if (destPort == SmsHeader.PORT_WAP_PUSH) { if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
mWapPush.dispatchWapPdu(sms.getUserData()); mWapPush.dispatchWapPdu(sms.getUserData());
} }
// The message was sent to a port, so concoct a URI for it // The message was sent to a port, so concoct a URI for it.
dispatchPortAddressedPdus(pdus, destPort); dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
} else { } else {
// It's a normal message, dispatch it // Normal short and non-port-addressed message, dispatch it.
dispatchPdus(pdus); dispatchPdus(pdus);
} }
} else { } else {
// Process the message part // Process the message part.
processMessagePart(sms, referenceNumber, sequence, count, destPort); processMessagePart(sms, smsHeader.concatRef, smsHeader.portAddrs);
} }
} }
@ -208,28 +142,30 @@ final class GsmSMSDispatcher extends SMSDispatcher {
protected void sendMultipartText(String destinationAddress, String scAddress, protected void sendMultipartText(String destinationAddress, String scAddress,
ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) { ArrayList<PendingIntent> deliveryIntents) {
int ref = ++sConcatenatedRef & 0xff;
for (int i = 0, count = parts.size(); i < count; i++) { int refNumber = getNextConcatenatedRef() & 0x00FF;
// build SmsHeader
byte[] data = new byte[3]; for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
data[0] = (byte) ref; // reference #, unique per message SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
data[1] = (byte) count; // total part count concatRef.refNumber = refNumber;
data[2] = (byte) (i + 1); // 1-based sequence concatRef.seqNumber = i + 1; // 1-based sequence
SmsHeader header = new SmsHeader(); concatRef.msgCount = msgCount;
header.add(new SmsHeader.Element(SmsHeader.CONCATENATED_8_BIT_REFERENCE, data)); concatRef.isEightBits = false;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
PendingIntent sentIntent = null; PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
if (sentIntents != null && sentIntents.size() > i) { if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i); sentIntent = sentIntents.get(i);
} }
PendingIntent deliveryIntent = null;
if (deliveryIntents != null && deliveryIntents.size() > i) { if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i); deliveryIntent = deliveryIntents.get(i);
} }
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, header.toByteArray()); parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader));
sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent); sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
} }
@ -259,18 +195,16 @@ final class GsmSMSDispatcher extends SMSDispatcher {
* to the recipient. The raw pdu of the status report is in the * to the recipient. The raw pdu of the status report is in the
* extended data ("pdu"). * extended data ("pdu").
*/ */
private void sendMultipartTextWithPermit(String destinationAddress, private void sendMultipartTextWithPermit(String destinationAddress,
String scAddress, ArrayList<String> parts, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> sentIntents,
ArrayList<PendingIntent> deliveryIntents) { ArrayList<PendingIntent> deliveryIntents) {
PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
// check if in service // check if in service
int ss = mPhone.getServiceState().getState(); int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) { if (ss != ServiceState.STATE_IN_SERVICE) {
for (int i = 0, count = parts.size(); i < count; i++) { for (int i = 0, count = parts.size(); i < count; i++) {
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) { if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i); sentIntent = sentIntents.get(i);
} }
@ -280,26 +214,29 @@ final class GsmSMSDispatcher extends SMSDispatcher {
return; return;
} }
int ref = ++sConcatenatedRef & 0xff; int refNumber = getNextConcatenatedRef() & 0x00FF;
for (int i = 0, count = parts.size(); i < count; i++) { for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
// build SmsHeader SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
byte[] data = new byte[3]; concatRef.refNumber = refNumber;
data[0] = (byte) ref; // reference #, unique per message concatRef.seqNumber = i + 1; // 1-based sequence
data[1] = (byte) count; // total part count concatRef.msgCount = msgCount;
data[2] = (byte) (i + 1); // 1-based sequence concatRef.isEightBits = false;
SmsHeader header = new SmsHeader(); SmsHeader smsHeader = new SmsHeader();
header.add(new SmsHeader.Element(SmsHeader.CONCATENATED_8_BIT_REFERENCE, data)); smsHeader.concatRef = concatRef;
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) { if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i); sentIntent = sentIntents.get(i);
} }
PendingIntent deliveryIntent = null;
if (deliveryIntents != null && deliveryIntents.size() > i) { if (deliveryIntents != null && deliveryIntents.size() > i) {
deliveryIntent = deliveryIntents.get(i); deliveryIntent = deliveryIntents.get(i);
} }
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, header.toByteArray()); parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader));
HashMap<String, Object> map = new HashMap<String, Object>(); HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", pdus.encodedScAddress); map.put("smsc", pdus.encodedScAddress);
@ -307,7 +244,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent); SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent);
sendSms(tracker); sendSms(tracker);
} }
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -376,4 +313,3 @@ final class GsmSMSDispatcher extends SMSDispatcher {
} }
} }

View File

@ -330,9 +330,20 @@ public class SmsMessage extends SmsMessageBase{
public static SubmitPdu getSubmitPdu(String scAddress, public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, short destinationPort, byte[] data, String destinationAddress, short destinationPort, byte[] data,
boolean statusReportRequested) { boolean statusReportRequested) {
if (data.length > (MAX_USER_DATA_BYTES - 7 /* UDH size */)) {
SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
portAddrs.destPort = destinationPort;
portAddrs.origPort = 0;
portAddrs.areEightBits = false;
SmsHeader smsHeader = new SmsHeader();
smsHeader.portAddrs = portAddrs;
byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
Log.e(LOG_TAG, "SMS data message may only contain " Log.e(LOG_TAG, "SMS data message may only contain "
+ (MAX_USER_DATA_BYTES - 7) + " bytes"); + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
return null; return null;
} }
@ -348,21 +359,12 @@ public class SmsMessage extends SmsMessageBase{
// (no TP-Validity-Period) // (no TP-Validity-Period)
// User data size // Total size
bo.write(data.length + 7); bo.write(data.length + smsHeaderData.length + 1);
// User data header size // User data header
bo.write(0x06); // header is 6 octets bo.write(smsHeaderData.length);
bo.write(smsHeaderData, 0, smsHeaderData.length);
// User data header, indicating the destination port
bo.write(SmsHeader.APPLICATION_PORT_ADDRESSING_16_BIT); // port
// addressing
// header
bo.write(0x04); // each port is 2 octets
bo.write((destinationPort >> 8) & 0xFF); // MSB of destination port
bo.write(destinationPort & 0xFF); // LSB of destination port
bo.write(0x00); // MSB of originating port
bo.write(0x00); // LSB of originating port
// User data // User data
bo.write(data, 0, data.length); bo.write(data, 0, data.length);
@ -562,7 +564,7 @@ public class SmsMessage extends SmsMessageBase{
byte[] udh = new byte[userDataHeaderLength]; byte[] udh = new byte[userDataHeaderLength];
System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength); System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
userDataHeader = SmsHeader.parse(udh); userDataHeader = SmsHeader.fromByteArray(udh);
offset += userDataHeaderLength; offset += userDataHeaderLength;
int headerBits = (userDataHeaderLength + 1) * 8; int headerBits = (userDataHeaderLength + 1) * 8;

View File

@ -135,6 +135,81 @@ public class CdmaSmsTest extends AndroidTestCase {
assertEquals(userData.payloadStr, revBearerData.userData.payloadStr); assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
} }
@SmallTest
public void testUserDataHeaderConcatRefFeedback() throws Exception {
BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
bearerData.messageId = 55;
SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
concatRef.refNumber = 0xEE;
concatRef.msgCount = 2;
concatRef.seqNumber = 2;
concatRef.isEightBits = true;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
byte[] encodedHeader = SmsHeader.toByteArray(smsHeader);
SmsHeader decodedHeader = SmsHeader.fromByteArray(encodedHeader);
assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
assertEquals(decodedHeader.portAddrs, null);
UserData userData = new UserData();
userData.payloadStr = "User Data Header (UDH) feedback test";
userData.userDataHeader = smsHeader;
bearerData.userData = userData;
byte[] encodedSms = BearerData.encode(bearerData);
BearerData revBearerData = BearerData.decode(encodedSms);
decodedHeader = revBearerData.userData.userDataHeader;
assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
assertEquals(decodedHeader.portAddrs, null);
}
@SmallTest
public void testUserDataHeaderMixedFeedback() throws Exception {
BearerData bearerData = new BearerData();
bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
bearerData.messageId = 42;
SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
concatRef.refNumber = 0x34;
concatRef.msgCount = 5;
concatRef.seqNumber = 2;
concatRef.isEightBits = false;
SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
portAddrs.destPort = 88;
portAddrs.origPort = 66;
portAddrs.areEightBits = false;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
smsHeader.portAddrs = portAddrs;
byte[] encodedHeader = SmsHeader.toByteArray(smsHeader);
SmsHeader decodedHeader = SmsHeader.fromByteArray(encodedHeader);
assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
assertEquals(decodedHeader.portAddrs.destPort, portAddrs.destPort);
assertEquals(decodedHeader.portAddrs.origPort, portAddrs.origPort);
assertEquals(decodedHeader.portAddrs.areEightBits, portAddrs.areEightBits);
UserData userData = new UserData();
userData.payloadStr = "User Data Header (UDH) feedback test";
userData.userDataHeader = smsHeader;
bearerData.userData = userData;
byte[] encodedSms = BearerData.encode(bearerData);
BearerData revBearerData = BearerData.decode(encodedSms);
decodedHeader = revBearerData.userData.userDataHeader;
assertEquals(decodedHeader.concatRef.refNumber, concatRef.refNumber);
assertEquals(decodedHeader.concatRef.msgCount, concatRef.msgCount);
assertEquals(decodedHeader.concatRef.seqNumber, concatRef.seqNumber);
assertEquals(decodedHeader.concatRef.isEightBits, concatRef.isEightBits);
assertEquals(decodedHeader.portAddrs.destPort, portAddrs.destPort);
assertEquals(decodedHeader.portAddrs.origPort, portAddrs.origPort);
assertEquals(decodedHeader.portAddrs.areEightBits, portAddrs.areEightBits);
}
@SmallTest @SmallTest
public void testReplyOption() throws Exception { public void testReplyOption() throws Exception {
String pdu1 = "0003104090011648b6a794e0705476bf77bceae934fe5f6d94d87450080a0180"; String pdu1 = "0003104090011648b6a794e0705476bf77bceae934fe5f6d94d87450080a0180";

View File

@ -69,10 +69,15 @@ public class SMSTest extends AndroidTestCase {
SmsHeader header = sms.getUserDataHeader(); SmsHeader header = sms.getUserDataHeader();
assertNotNull(header); assertNotNull(header);
assertNotNull(header.concatRef);
Iterator<SmsHeader.Element> elements = header.getElements().iterator(); assertEquals(header.concatRef.refNumber, 42);
assertNotNull(elements); assertEquals(header.concatRef.msgCount, 2);
assertEquals(header.concatRef.seqNumber, 1);
assertEquals(header.concatRef.isEightBits, true);
assertNotNull(header.portAddrs);
assertEquals(header.portAddrs.destPort, 2948);
assertEquals(header.portAddrs.origPort, 9200);
assertEquals(header.portAddrs.areEightBits, false);
pdu = "07914140279510F6440A8111110301003BF56080207130238A3B0B05040B8423F" pdu = "07914140279510F6440A8111110301003BF56080207130238A3B0B05040B8423F"
+ "000032A0202362E3130322E3137312E3135302F524E453955304A6D7135514141" + "000032A0202362E3130322E3137312E3135302F524E453955304A6D7135514141"
@ -81,9 +86,15 @@ public class SMSTest extends AndroidTestCase {
header = sms.getUserDataHeader(); header = sms.getUserDataHeader();
assertNotNull(header); assertNotNull(header);
assertNotNull(header.concatRef);
elements = header.getElements().iterator(); assertEquals(header.concatRef.refNumber, 42);
assertNotNull(elements); assertEquals(header.concatRef.msgCount, 2);
assertEquals(header.concatRef.seqNumber, 2);
assertEquals(header.concatRef.isEightBits, true);
assertNotNull(header.portAddrs);
assertEquals(header.portAddrs.destPort, 2948);
assertEquals(header.portAddrs.origPort, 9200);
assertEquals(header.portAddrs.areEightBits, false);
/* /*
* UCS-2 encoded SMS * UCS-2 encoded SMS

View File

@ -28,18 +28,20 @@ public class GsmAlphabetTest extends TestCase {
@SmallTest @SmallTest
public void test7bitWithHeader() throws Exception { public void test7bitWithHeader() throws Exception {
byte[] data = new byte[3]; SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
data[0] = (byte) 1; concatRef.refNumber = 1;
data[1] = (byte) 2; concatRef.seqNumber = 2;
data[2] = (byte) 2; concatRef.msgCount = 2;
concatRef.isEightBits = true;
SmsHeader header = new SmsHeader(); SmsHeader header = new SmsHeader();
header.add(new SmsHeader.Element(SmsHeader.CONCATENATED_8_BIT_REFERENCE, data)); header.concatRef = concatRef;
String message = "aaaaaaaaaabbbbbbbbbbcccccccccc"; String message = "aaaaaaaaaabbbbbbbbbbcccccccccc";
byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header.toByteArray()); byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message,
SmsHeader.toByteArray(header));
int septetCount = GsmAlphabet.countGsmSeptets(message, false); int septetCount = GsmAlphabet.countGsmSeptets(message, false);
String parsedMessage = GsmAlphabet.gsm7BitPackedToString( String parsedMessage = GsmAlphabet.gsm7BitPackedToString(
userData, header.toByteArray().length+1, septetCount, 1); userData, SmsHeader.toByteArray(header).length+1, septetCount, 1);
assertEquals(message, parsedMessage); assertEquals(message, parsedMessage);
} }
@ -306,4 +308,3 @@ public class GsmAlphabetTest extends TestCase {
GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1)); GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
} }
} }

View File

@ -34,35 +34,38 @@ public class SMSDispatcherTest extends AndroidTestCase {
public void testCMT1() throws Exception { public void testCMT1() throws Exception {
SmsMessage sms; SmsMessage sms;
SmsHeader header; SmsHeader header;
Iterator<SmsHeader.Element> elements;
String[] lines = new String[2]; String[] lines = new String[2];
lines[0] = "+CMT: ,158"; lines[0] = "+CMT: ,158";
lines[1] = "07914140279510F6440A8111110301003BF56080426101748A8C0B05040B" lines[1] = "07914140279510F6440A8111110301003BF56080426101748A8C0B05040B"
+ "8423F000035502010106276170706C69636174696F6E2F766E642E776170" + "8423F000035502010106276170706C69636174696F6E2F766E642E776170"
+ "2E6D6D732D6D65737361676500AF848D0185B4848C8298524F347839776F" + "2E6D6D732D6D65737361676500AF848D0185B4848C8298524F347839776F"
+ "7547514D4141424C3641414141536741415A4B554141414141008D908918" + "7547514D4141424C3641414141536741415A4B554141414141008D908918"
+ "802B31363530323438363137392F545950453D504C4D4E008A808E028000" + "802B31363530323438363137392F545950453D504C4D4E008A808E028000"
+ "88058103093A8083687474703A2F2F36"; + "88058103093A8083687474703A2F2F36";
sms = SmsMessage.newFromCMT(lines); sms = SmsMessage.newFromCMT(lines);
header = sms.getUserDataHeader(); header = sms.getUserDataHeader();
assertNotNull(header); assertNotNull(header);
assertNotNull(sms.getUserData()); assertNotNull(sms.getUserData());
assertNotNull(header.concatRef);
elements = header.getElements().iterator(); assertEquals(header.concatRef.refNumber, 85);
assertNotNull(elements); assertEquals(header.concatRef.msgCount, 2);
assertEquals(header.concatRef.seqNumber, 1);
assertEquals(header.concatRef.isEightBits, true);
assertNotNull(header.portAddrs);
assertEquals(header.portAddrs.destPort, 2948);
assertEquals(header.portAddrs.origPort, 9200);
assertEquals(header.portAddrs.areEightBits, false);
} }
@MediumTest @MediumTest
public void testCMT2() throws Exception { public void testCMT2() throws Exception {
SmsMessage sms; SmsMessage sms;
SmsHeader header; SmsHeader header;
Iterator<SmsHeader.Element> elements;
String[] lines = new String[2]; String[] lines = new String[2];
lines[0] = "+CMT: ,77"; lines[0] = "+CMT: ,77";
lines[1] = "07914140279510F6440A8111110301003BF56080426101848A3B0B05040B8423F" lines[1] = "07914140279510F6440A8111110301003BF56080426101848A3B0B05040B8423F"
@ -71,12 +74,17 @@ public class SMSDispatcherTest extends AndroidTestCase {
sms = SmsMessage.newFromCMT(lines); sms = SmsMessage.newFromCMT(lines);
header = sms.getUserDataHeader(); header = sms.getUserDataHeader();
System.out.println("header = " + header);
assertNotNull(header); assertNotNull(header);
assertNotNull(sms.getUserData()); assertNotNull(sms.getUserData());
assertNotNull(header.concatRef);
elements = header.getElements().iterator(); assertEquals(header.concatRef.refNumber, 85);
assertNotNull(elements); assertEquals(header.concatRef.msgCount, 2);
assertEquals(header.concatRef.seqNumber, 2);
assertEquals(header.concatRef.isEightBits, true);
assertNotNull(header.portAddrs);
assertEquals(header.portAddrs.destPort, 2948);
assertEquals(header.portAddrs.origPort, 9200);
assertEquals(header.portAddrs.areEightBits, false);
} }
@MediumTest @MediumTest