am 4d53cb02: Merge "Enable support for SMS national language shift tables." into gingerbread

* commit '4d53cb02168fe35104d7e644dd9b3efd8ca4c91b':
  Enable support for SMS national language shift tables.
This commit is contained in:
Jake Hamby
2011-05-05 13:31:03 -07:00
committed by Android Git Automerger
14 changed files with 1677 additions and 464 deletions

View File

@@ -426,6 +426,46 @@
option to enable/disable read reports is removed in the Messaging app. -->
<bool name="config_mms_read_reports_support">true</bool>
<!-- National Language Identifier codes for the following two config items.
(from 3GPP TS 23.038 V9.1.1 Table 6.2.1.2.4.1):
0 - reserved
1 - Turkish
2 - Spanish (single shift table only)
3 - Portuguese
4 - Bengali
5 - Gujarati
6 - Hindi
7 - Kannada
8 - Malayalam
9 - Oriya
10 - Punjabi
11 - Tamil
12 - Telugu
13 - Urdu
14+ - reserved -->
<!-- National language single shift tables to enable for SMS encoding.
Decoding is always enabled. 3GPP TS 23.038 states that this feature
should not be enabled until a formal request is issued by the relevant
national regulatory body. Array elements are codes from the table above.
Example 1: devices sold in Turkey must include table 1 to conform with
By-Law Number 27230. (http://www.btk.gov.tr/eng/pdf/2009/BY-LAW_SMS.pdf)
Example 2: devices sold in India should include tables 4 through 13
to enable use of the new Release 9 tables for Indic languages. -->
<integer-array name="config_sms_enabled_single_shift_tables"></integer-array>
<!-- National language locking shift tables to enable for SMS encoding.
Decoding is always enabled. 3GPP TS 23.038 states that this feature
should not be enabled until a formal request is issued by the relevant
national regulatory body. Array elements are codes from the table above.
Example 1: devices sold in Turkey must include table 1 after the
Turkish Telecommunication Authority requires locking shift encoding
to be enabled (est. July 2012). (http://www.btk.gov.tr/eng/pdf/2009/BY-LAW_SMS.pdf)
See also: http://www.mobitech.com.tr/tr/ersanozturkblog_en/index.php?entry=entry090223-160014
Example 2: devices sold in India should include tables 4 through 13
to enable use of the new Release 9 tables for Indic languages. -->
<integer-array name="config_sms_enabled_locking_shift_tables"></integer-array>
<!-- Set and Unsets WiMAX -->
<bool name="config_wimaxEnabled">false</bool>
<!-- Location of the wimax framwork jar location -->

View File

@@ -47,30 +47,25 @@ import android.os.SystemClock;
import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Telephony.Sms.Intents;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.telephony.SmsMessage;
import android.util.Log;
import android.util.SparseIntArray;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.Phone;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.util.HexDump;
import com.android.internal.telephony.Phone;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringBufferInputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
/**

View File

@@ -314,7 +314,8 @@ public class SmsMessage {
nextPos = pos + Math.min(limit, textLen - pos);
} else {
// For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit);
nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit,
ted.languageTable, ted.languageShiftTable);
}
} else { // Assume unicode.
nextPos = pos + Math.min(limit / 2, textLen - pos);
@@ -370,7 +371,8 @@ public class SmsMessage {
*/
/**
* Get an SMS-SUBMIT PDU for a destination address and a message
* Get an SMS-SUBMIT PDU for a destination address and a message.
* This method will not attempt to use any GSM national language 7 bit encodings.
*
* @param scAddress Service Centre address. Null means use default.
* @return a <code>SubmitPdu</code> containing the encoded SC
@@ -397,7 +399,8 @@ public class SmsMessage {
}
/**
* Get an SMS-SUBMIT PDU for a destination address and a message
* Get an SMS-SUBMIT PDU for a destination address and a message.
* This method will not attempt to use any GSM national language 7 bit encodings.
*
* @param scAddress Service Centre address. Null means use default.
* @return a <code>SubmitPdu</code> containing the encoded SC
@@ -421,7 +424,8 @@ public class SmsMessage {
}
/**
* 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.
* This method will not attempt to use any GSM national language 7 bit encodings.
*
* @param scAddress Service Centre address. null == use default
* @param destinationAddress the address of the destination for the message

View File

@@ -297,37 +297,14 @@ public class SmsMessage {
*/
@Deprecated
public static int[] calculateLength(CharSequence messageBody, boolean use7bitOnly) {
SmsMessageBase.TextEncodingDetails ted =
com.android.internal.telephony.gsm.SmsMessage
.calculateLength(messageBody, use7bitOnly);
int ret[] = new int[4];
try {
// Try GSM alphabet
int septets = GsmAlphabet.countGsmSeptets(messageBody, !use7bitOnly);
ret[1] = septets;
if (septets > MAX_USER_DATA_SEPTETS) {
ret[0] = (septets + (MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) /
MAX_USER_DATA_SEPTETS_WITH_HEADER;
ret[2] = (ret[0] * MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets;
} else {
ret[0] = 1;
ret[2] = MAX_USER_DATA_SEPTETS - septets;
}
ret[3] = ENCODING_7BIT;
} catch (EncodeException ex) {
// fall back to UCS-2
int octets = messageBody.length() * 2;
ret[1] = messageBody.length();
if (octets > MAX_USER_DATA_BYTES) {
// 6 is the size of the user data header
ret[0] = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
MAX_USER_DATA_BYTES_WITH_HEADER;
ret[2] = ((ret[0] * MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
} else {
ret[0] = 1;
ret[2] = (MAX_USER_DATA_BYTES - octets)/2;
}
ret[3] = ENCODING_16BIT;
}
ret[0] = ted.msgCount;
ret[1] = ted.codeUnitCount;
ret[2] = ted.codeUnitsRemaining;
ret[3] = ted.codeUnitSize;
return ret;
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ import java.util.ArrayList;
*/
public class SmsHeader {
// TODO(cleanup): this datastructure is generally referred to as
// TODO(cleanup): this data structure is generally referred to as
// the 'user data header' or UDH, and so the class name should
// change to reflect this...
@@ -66,6 +66,8 @@ public class SmsHeader {
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 ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
public static final int PORT_WAP_PUSH = 2948;
public static final int PORT_WAP_WSP = 9200;
@@ -96,6 +98,12 @@ public class SmsHeader {
public ConcatRef concatRef;
public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
/** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
public int languageTable;
/** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
public int languageShiftTable;
public SmsHeader() {}
/**
@@ -157,6 +165,12 @@ public class SmsHeader {
portAddrs.areEightBits = false;
smsHeader.portAddrs = portAddrs;
break;
case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
smsHeader.languageShiftTable = inStream.read();
break;
case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
smsHeader.languageTable = inStream.read();
break;
default:
MiscElt miscElt = new MiscElt();
miscElt.id = id;
@@ -212,6 +226,16 @@ public class SmsHeader {
outStream.write(portAddrs.origPort & 0x00FF);
}
}
if (smsHeader.languageShiftTable != 0) {
outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
outStream.write(1);
outStream.write(smsHeader.languageShiftTable);
}
if (smsHeader.languageTable != 0) {
outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
outStream.write(1);
outStream.write(smsHeader.languageTable);
}
for (MiscElt miscElt : smsHeader.miscEltList) {
outStream.write(miscElt.id);
outStream.write(miscElt.data.length);
@@ -243,6 +267,12 @@ public class SmsHeader {
builder.append(", areEightBits=" + portAddrs.areEightBits);
builder.append(" }");
}
if (languageShiftTable != 0) {
builder.append(", languageShiftTable=" + languageShiftTable);
}
if (languageTable != 0) {
builder.append(", languageTable=" + languageTable);
}
for (MiscElt miscElt : miscEltList) {
builder.append(", MiscElt ");
builder.append("{ id=" + miscElt.id);

View File

@@ -118,6 +118,16 @@ public abstract class SmsMessageBase {
*/
public int codeUnitSize;
/**
* The GSM national language table to use, or 0 for the default 7-bit alphabet.
*/
public int languageTable;
/**
* The GSM national language shift table to use, or 0 for the default 7-bit extension table.
*/
public int languageShiftTable;
@Override
public String toString() {
return "TextEncodingDetails " +
@@ -125,6 +135,8 @@ public abstract class SmsMessageBase {
", codeUnitCount=" + codeUnitCount +
", codeUnitsRemaining=" + codeUnitsRemaining +
", codeUnitSize=" + codeUnitSize +
", languageTable=" + languageTable +
", languageShiftTable=" + languageShiftTable +
" }";
}
}

View File

@@ -111,7 +111,7 @@ class GetInkeyInputResponseData extends ResponseData {
int size = mInData.length();
byte[] tempData = GsmAlphabet
.stringToGsm7BitPacked(mInData);
.stringToGsm7BitPacked(mInData, 0, 0);
data = new byte[size];
// Since stringToGsm7BitPacked() set byte 0 in the
// returned byte array to the count of septets used...

View File

@@ -21,7 +21,6 @@ import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
import android.util.Log;
import android.util.SparseIntArray;
import android.telephony.SmsMessage;
@@ -30,10 +29,8 @@ import android.text.format.Time;
import com.android.internal.telephony.IccUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.HexDump;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
@@ -504,7 +501,7 @@ public final class BearerData {
* stringToGsm7BitPacked, and potentially directly support
* access to the main bitwise stream from encode/decode.
*/
byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force);
byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
Gsm7bitCodingResult result = new Gsm7bitCodingResult();
result.data = new byte[fullData.length - 1];
System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
@@ -927,7 +924,7 @@ public final class BearerData {
private static String decodeUtf16(byte[] data, int offset, int numFields)
throws CodingException
{
// Start reading from the next 16-bit aligned boundry after offset.
// Start reading from the next 16-bit aligned boundary after offset.
int padding = offset % 2;
numFields -= (offset + padding) / 2;
try {
@@ -973,12 +970,13 @@ public final class BearerData {
private static String decode7bitGsm(byte[] data, int offset, int numFields)
throws CodingException
{
// Start reading from the next 7-bit aligned boundry after offset.
// Start reading from the next 7-bit aligned boundary after offset.
int offsetBits = offset * 8;
int offsetSeptets = (offsetBits + 6) / 7;
numFields -= offsetSeptets;
int paddingBits = (offsetSeptets * 7) - offsetBits;
String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits);
String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
0, 0);
if (result == null) {
throw new CodingException("7bit GSM decoding failed");
}

View File

@@ -69,9 +69,8 @@ final class GsmSMSDispatcher extends SMSDispatcher {
String pduString = (String) ar.result;
SmsMessage sms = SmsMessage.newFromCDS(pduString);
int tpStatus = sms.getStatus();
if (sms != null) {
int tpStatus = sms.getStatus();
int messageRef = sms.messageRef;
for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
SmsTracker tracker = deliveryPendingList.get(i);
@@ -190,6 +189,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
mRemainingMessages = msgCount;
TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
for (int i = 0; i < msgCount; i++) {
TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
if (encoding != details.codeUnitSize
@@ -197,6 +197,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
|| encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
encoding = details.codeUnitSize;
}
encodingForParts[i] = details;
}
for (int i = 0; i < msgCount; i++) {
@@ -213,6 +214,10 @@ final class GsmSMSDispatcher extends SMSDispatcher {
concatRef.isEightBits = true;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
smsHeader.languageTable = encodingForParts[i].languageTable;
smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
}
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) {
@@ -226,7 +231,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
encoding);
encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
}
@@ -281,6 +286,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
mRemainingMessages = msgCount;
TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
for (int i = 0; i < msgCount; i++) {
TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
if (encoding != details.codeUnitSize
@@ -288,6 +294,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
|| encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
encoding = details.codeUnitSize;
}
encodingForParts[i] = details;
}
for (int i = 0; i < msgCount; i++) {
@@ -298,6 +305,10 @@ final class GsmSMSDispatcher extends SMSDispatcher {
concatRef.isEightBits = false;
SmsHeader smsHeader = new SmsHeader();
smsHeader.concatRef = concatRef;
if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
smsHeader.languageTable = encodingForParts[i].languageTable;
smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
}
PendingIntent sentIntent = null;
if (sentIntents != null && sentIntents.size() > i) {
@@ -311,7 +322,7 @@ final class GsmSMSDispatcher extends SMSDispatcher {
SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
encoding);
encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("smsc", pdus.encodedScAddress);

View File

@@ -27,7 +27,6 @@ import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SimRegionCache;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
@@ -39,7 +38,6 @@ import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER;
import static android.telephony.SmsMessage.MessageClass;
/**
@@ -221,9 +219,7 @@ public class SmsMessage extends SmsMessageBase{
*/
public static int getTPLayerLengthForPDU(String pdu) {
int len = pdu.length() / 2;
int smscLen = 0;
smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
return len - smscLen - 1;
}
@@ -241,7 +237,7 @@ public class SmsMessage extends SmsMessageBase{
String destinationAddress, String message,
boolean statusReportRequested, byte[] header) {
return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
ENCODING_UNKNOWN);
ENCODING_UNKNOWN, 0, 0);
}
@@ -251,6 +247,8 @@ public class SmsMessage extends SmsMessageBase{
*
* @param scAddress Service Centre address. Null means use default.
* @param encoding Encoding defined by constants in android.telephony.SmsMessage.ENCODING_*
* @param languageTable
* @param languageShiftTable
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
@@ -258,7 +256,8 @@ public class SmsMessage extends SmsMessageBase{
*/
public static SubmitPdu getSubmitPdu(String scAddress,
String destinationAddress, String message,
boolean statusReportRequested, byte[] header, int encoding) {
boolean statusReportRequested, byte[] header, int encoding,
int languageTable, int languageShiftTable) {
// Perform null parameter checks.
if (message == null || destinationAddress == null) {
@@ -279,7 +278,8 @@ public class SmsMessage extends SmsMessageBase{
}
try {
if (encoding == ENCODING_7BIT) {
userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header);
userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
languageTable, languageShiftTable);
} else { //assume UCS-2
try {
userData = encodeUCS2(message, header);
@@ -384,7 +384,7 @@ public class SmsMessage extends SmsMessageBase{
* @param destinationAddress the address of the destination for the message
* @param destinationPort the port to deliver the message to at the
* destination
* @param data the dat for the message
* @param data the data for the message
* @return a <code>SubmitPdu</code> containing the encoded SC
* address, if applicable, and the encoded message.
* Returns null on encode error.
@@ -580,7 +580,7 @@ public class SmsMessage extends SmsMessageBase{
int second = IccUtils.gsmBcdByteToInt(pdu[cur++]);
// For the timezone, the most significant bit of the
// least signficant nibble is the sign byte
// least significant nibble is the sign byte
// (meaning the max range of this field is 79 quarter-hours,
// which is more than enough)
@@ -639,7 +639,7 @@ public class SmsMessage extends SmsMessageBase{
/*
* Here we just create the user data length to be the remainder of
* the pdu minus the user data header, since userDataLength means
* the number of uncompressed sepets.
* the number of uncompressed septets.
*/
bufferLen = pdu.length - offset;
} else {
@@ -697,70 +697,19 @@ public class SmsMessage extends SmsMessageBase{
return userDataHeader;
}
/*
XXX Not sure what this one is supposed to be doing, and no one is using
it.
String getUserDataGSM8bit() {
// System.out.println("remainder of pud:" +
// HexDump.dumpHexString(pdu, cur, pdu.length - cur));
int count = pdu[cur++] & 0xff;
int size = pdu[cur++];
// skip over header for now
cur += size;
if (pdu[cur - 1] == 0x01) {
int tid = pdu[cur++] & 0xff;
int type = pdu[cur++] & 0xff;
size = pdu[cur++] & 0xff;
int i = cur;
while (pdu[i++] != '\0') {
}
int length = i - cur;
String mimeType = new String(pdu, cur, length);
cur += length;
if (false) {
System.out.println("tid = 0x" + HexDump.toHexString(tid));
System.out.println("type = 0x" + HexDump.toHexString(type));
System.out.println("header size = " + size);
System.out.println("mimeType = " + mimeType);
System.out.println("remainder of header:" +
HexDump.dumpHexString(pdu, cur, (size - mimeType.length())));
}
cur += size - mimeType.length();
// System.out.println("data count = " + count + " cur = " + cur
// + " :" + HexDump.dumpHexString(pdu, cur, pdu.length - cur));
MMSMessage msg = MMSMessage.parseEncoding(mContext, pdu, cur,
pdu.length - cur);
} else {
System.out.println(new String(pdu, cur, pdu.length - cur - 1));
}
return IccUtils.bytesToHexString(pdu);
}
*/
/**
* Interprets the user data payload as pack GSM 7bit characters, and
* Interprets the user data payload as packed GSM 7bit characters, and
* decodes them into a String.
*
* @param septetCount the number of septets in the user data payload
* @return a String with the decoded characters
*/
String getUserDataGSM7Bit(int septetCount) {
String getUserDataGSM7Bit(int septetCount, int languageTable,
int languageShiftTable) {
String ret;
ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
mUserDataSeptetPadding);
mUserDataSeptetPadding, languageTable, languageShiftTable);
cur += (septetCount * 7) / 8;
@@ -824,21 +773,9 @@ public class SmsMessage extends SmsMessageBase{
*/
public static TextEncodingDetails calculateLength(CharSequence msgBody,
boolean use7bitOnly) {
TextEncodingDetails ted = new TextEncodingDetails();
try {
int septets = GsmAlphabet.countGsmSeptets(msgBody, !use7bitOnly);
ted.codeUnitCount = septets;
if (septets > MAX_USER_DATA_SEPTETS) {
ted.msgCount = (septets + (MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) /
MAX_USER_DATA_SEPTETS_WITH_HEADER;
ted.codeUnitsRemaining = (ted.msgCount *
MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets;
} else {
ted.msgCount = 1;
ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS - septets;
}
ted.codeUnitSize = ENCODING_7BIT;
} catch (EncodeException ex) {
TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly);
if (ted == null) {
ted = new TextEncodingDetails();
int octets = msgBody.length() * 2;
ted.codeUnitCount = msgBody.length();
if (octets > MAX_USER_DATA_BYTES) {
@@ -875,7 +812,7 @@ public class SmsMessage extends SmsMessageBase{
/** {@inheritDoc} */
public boolean isMWIClearMessage() {
if (isMwi && (mwiSense == false)) {
if (isMwi && !mwiSense) {
return true;
}
@@ -885,7 +822,7 @@ public class SmsMessage extends SmsMessageBase{
/** {@inheritDoc} */
public boolean isMWISetMessage() {
if (isMwi && (mwiSense == true)) {
if (isMwi && mwiSense) {
return true;
}
@@ -931,13 +868,13 @@ public class SmsMessage extends SmsMessageBase{
* 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
* hexad number (e.g. octet with integer value 42 is presented to TE as two
* 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...
*/
private void parsePdu(byte[] pdu) {
mPdu = pdu;
// Log.d(LOG_TAG, "raw sms mesage:");
// Log.d(LOG_TAG, "raw sms message:");
// Log.d(LOG_TAG, s);
PduParser p = new PduParser(pdu);
@@ -1158,7 +1095,9 @@ public class SmsMessage extends SmsMessageBase{
break;
case ENCODING_7BIT:
messageBody = p.getUserDataGSM7Bit(count);
messageBody = p.getUserDataGSM7Bit(count,
hasUserDataHeader ? userDataHeader.languageTable : 0,
hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
break;
case ENCODING_16BIT:

View File

@@ -20,7 +20,6 @@ import junit.framework.TestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
public class GsmAlphabetTest extends TestCase {
@@ -38,20 +37,21 @@ public class GsmAlphabetTest extends TestCase {
String message = "aaaaaaaaaabbbbbbbbbbcccccccccc";
byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message,
SmsHeader.toByteArray(header));
int septetCount = GsmAlphabet.countGsmSeptets(message, false);
SmsHeader.toByteArray(header), 0, 0);
int septetCount = GsmAlphabet.countGsmSeptetsUsingTables(message, true, 0, 0);
String parsedMessage = GsmAlphabet.gsm7BitPackedToString(
userData, SmsHeader.toByteArray(header).length+2, septetCount, 1);
userData, SmsHeader.toByteArray(header).length+2, septetCount, 1, 0, 0);
assertEquals(message, parsedMessage);
}
// TODO: This method should *really* be a series of individual test methods.
@LargeTest
// However, it's a SmallTest because it executes quickly.
@SmallTest
public void testBasic() throws Exception {
// '@' maps to char 0
assertEquals(0, GsmAlphabet.charToGsm('@'));
// `a (a with grave accent) maps to last GSM charater
// `a (a with grave accent) maps to last GSM character
assertEquals(0x7f, GsmAlphabet.charToGsm('\u00e0'));
//
@@ -97,7 +97,7 @@ public class GsmAlphabetTest extends TestCase {
assertEquals('@', GsmAlphabet.gsmToChar(0));
// `a (a with grave accent) maps to last GSM charater
// `a (a with grave accent) maps to last GSM character
assertEquals('\u00e0', GsmAlphabet.gsmToChar(0x7f));
assertEquals('\uffff',
@@ -116,8 +116,12 @@ public class GsmAlphabetTest extends TestCase {
assertEquals(' ', GsmAlphabet.gsmExtendedToChar(
GsmAlphabet.GSM_EXTENDED_ESCAPE));
// Unmappable
assertEquals(' ', GsmAlphabet.gsmExtendedToChar(0));
// Reserved for extension to extension table (mapped to space)
assertEquals(' ', GsmAlphabet.gsmExtendedToChar(GsmAlphabet.GSM_EXTENDED_ESCAPE));
// Unmappable (mapped to character in default or national locking shift table)
assertEquals('@', GsmAlphabet.gsmExtendedToChar(0));
assertEquals('\u00e0', GsmAlphabet.gsmExtendedToChar(0x7f));
//
// stringTo7BitPacked, gsm7BitPackedToString
@@ -128,7 +132,7 @@ public class GsmAlphabetTest extends TestCase {
// Check all alignment cases
for (int i = 0; i < 9; i++, testString.append('@')) {
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString());
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
assertEquals(testString.toString(),
GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
}
@@ -149,7 +153,7 @@ public class GsmAlphabetTest extends TestCase {
assertEquals(1, GsmAlphabet.countGsmSeptets(c));
}
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString());
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
assertEquals(testString.toString(),
GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
@@ -164,7 +168,7 @@ public class GsmAlphabetTest extends TestCase {
}
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString());
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
assertEquals(testString.toString(),
GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
@@ -175,7 +179,7 @@ public class GsmAlphabetTest extends TestCase {
testString.append('@');
}
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString());
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
assertEquals(testString.toString(),
GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
@@ -183,7 +187,7 @@ public class GsmAlphabetTest extends TestCase {
testString.append('@');
try {
GsmAlphabet.stringToGsm7BitPacked(testString.toString());
GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
fail("expected exception");
} catch (EncodeException ex) {
// exception expected
@@ -196,7 +200,7 @@ public class GsmAlphabetTest extends TestCase {
testString.append('{');
}
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString());
packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
assertEquals(testString.toString(),
GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
@@ -204,17 +208,29 @@ public class GsmAlphabetTest extends TestCase {
testString.append('{');
try {
GsmAlphabet.stringToGsm7BitPacked(testString.toString());
GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
fail("expected exception");
} catch (EncodeException ex) {
// exception expected
}
// Reserved for extension to extension table (mapped to space)
packed = new byte[]{(byte)(0x1b | 0x80), 0x1b >> 1};
assertEquals(" ", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
// Unmappable (mapped to character in default alphabet table)
packed[0] = 0x1b;
packed[1] = 0x00;
assertEquals("@", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
packed[0] = (byte)(0x1b | 0x80);
packed[1] = (byte)(0x7f >> 1);
assertEquals("\u00e0", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
//
// 8 bit unpacked format
//
// Note: we compare hex strings here
// because Assert doesnt have array-comparisons
// because Assert doesn't have array comparisons
byte unpacked[];
@@ -306,5 +322,16 @@ public class GsmAlphabetTest extends TestCase {
assertEquals("a",
GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));
// Reserved for extension to extension table (mapped to space)
unpacked[0] = 0x1b;
unpacked[1] = 0x1b;
assertEquals(" ", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
// Unmappable (mapped to character in default or national locking shift table)
unpacked[1] = 0x00;
assertEquals("@", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
unpacked[1] = 0x7f;
assertEquals("\u00e0", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
}
}

View File

@@ -16,16 +16,12 @@
package com.android.internal.telephony;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.gsm.SmsMessage;
import com.android.internal.util.HexDump;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
public class GsmSmsTest extends AndroidTestCase {
@SmallTest
@@ -211,8 +207,38 @@ public class GsmSmsTest extends AndroidTestCase {
sms.getMessageBody());
}
// GSM 7 bit tables in String form, Escape (0x1B) replaced with '@'
private static final String[] sBasicTables = {
// GSM 7 bit default alphabet
"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5\u0394_"
+ "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e@\u00c6\u00e6\u00df\u00c9"
+ " !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6"
+ "\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
// Turkish locking shift table
"@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5\u0394_"
+ "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e@\u015e\u015f\u00df\u00c9"
+ " !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u0130ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6"
+ "\u00d1\u00dc\u00a7\u00e7abcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
// no locking shift table defined for Spanish
"",
// Portuguese locking shift table
"@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1\u0394_"
+ "\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|@\u00c2\u00e2\u00ca\u00c9 !\"#\u00ba%&'()"
+ "*+,-./0123456789:;<=>?\u00cdABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc"
+ "\u00a7~abcdefghijklmnopqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0"
};
@SmallTest
public void testDecode() throws Exception {
decodeSingle(0); // default table
decodeSingle(1); // Turkish locking shift table
decodeSingle(3); // Portuguese locking shift table
}
private void decodeSingle(int language) throws Exception {
byte[] septets = new byte[(7 * 128 + 7) / 8];
int bitOffset = 0;
@@ -238,15 +264,168 @@ public class GsmSmsTest extends AndroidTestCase {
bitOffset += 7;
}
String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, 128);
byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded);
String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, 128, 0, language, 0);
byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded, language, 0);
assertEquals(sBasicTables[language], decoded);
// reEncoded has the count septets byte at the front
assertEquals(reEncoded.length, septets.length + 1);
assertEquals(septets.length + 1, reEncoded.length);
for (int i = 0; i < septets.length; i++) {
assertEquals(reEncoded[i + 1], septets[i]);
assertEquals(septets[i], reEncoded[i + 1]);
}
}
private static final int GSM_ESCAPE_CHARACTER = 0x1b;
private static final String[] sExtendedTables = {
// GSM 7 bit default alphabet extension table
"\f^{}\\[~]|\u20ac",
// Turkish single shift extension table
"\f^{}\\[~]|\u011e\u0130\u015e\u00e7\u20ac\u011f\u0131\u015f",
// Spanish single shift extension table
"\u00e7\f^{}\\[~]|\u00c1\u00cd\u00d3\u00da\u00e1\u20ac\u00ed\u00f3\u00fa",
// Portuguese single shift extension table
"\u00ea\u00e7\f\u00d4\u00f4\u00c1\u00e1\u03a6\u0393^\u03a9\u03a0\u03a8\u03a3\u0398\u00ca"
+ "{}\\[~]|\u00c0\u00cd\u00d3\u00da\u00c3\u00d5\u00c2\u20ac\u00ed\u00f3\u00fa\u00e3"
+ "\u00f5\u00e2"
};
private static final int[][] sExtendedTableIndexes = {
{0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x65},
{0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x47, 0x49, 0x53, 0x63,
0x65, 0x67, 0x69, 0x73},
{0x09, 0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x49, 0x4f,
0x55, 0x61, 0x65, 0x69, 0x6f, 0x75},
{0x05, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1f, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x49,
0x4f, 0x55, 0x5b, 0x5c, 0x61, 0x65, 0x69, 0x6f, 0x75, 0x7b, 0x7c, 0x7f}
};
@SmallTest
public void testDecodeExtended() throws Exception {
for (int language = 0; language < 3; language++) {
int[] tableIndex = sExtendedTableIndexes[language];
int numSeptets = tableIndex.length * 2; // two septets per extended char
byte[] septets = new byte[(7 * numSeptets + 7) / 8];
int bitOffset = 0;
for (int v : tableIndex) {
// escape character
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
septets[byteOffset] |= GSM_ESCAPE_CHARACTER << shift;
if (shift > 1) {
septets[byteOffset + 1] = (byte) (GSM_ESCAPE_CHARACTER >> (8 - shift));
}
bitOffset += 7;
// extended table index
byteOffset = bitOffset / 8;
shift = bitOffset % 8;
septets[byteOffset] |= v << shift;
if (shift > 1) {
septets[byteOffset + 1] = (byte) (v >> (8 - shift));
}
bitOffset += 7;
}
String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0,
0, language);
byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded, 0, language);
assertEquals(sExtendedTables[language], decoded);
// reEncoded has the count septets byte at the front
assertEquals(septets.length + 1, reEncoded.length);
for (int i = 0; i < septets.length; i++) {
assertEquals(septets[i], reEncoded[i + 1]);
}
}
}
@SmallTest
public void testDecodeExtendedFallback() throws Exception {
// verify that unmapped characters in extension table fall back to locking shift table
for (int language = 0; language < 3; language++) {
int[] tableIndex = sExtendedTableIndexes[language];
int numChars = 128 - tableIndex.length;
int numSeptets = numChars * 2; // two septets per extended char
byte[] septets = new byte[(7 * numSeptets + 7) / 8];
int tableOffset = 0;
int bitOffset = 0;
StringBuilder defaultTable = new StringBuilder(128);
StringBuilder turkishTable = new StringBuilder(128);
StringBuilder portugueseTable = new StringBuilder(128);
for (char c = 0; c < 128; c++) {
// skip characters that are present in the current extension table
if (tableOffset < tableIndex.length && tableIndex[tableOffset] == c) {
tableOffset++;
continue;
}
// escape character
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
septets[byteOffset] |= GSM_ESCAPE_CHARACTER << shift;
if (shift > 1) {
septets[byteOffset + 1] = (byte) (GSM_ESCAPE_CHARACTER >> (8 - shift));
}
bitOffset += 7;
// extended table index
byteOffset = bitOffset / 8;
shift = bitOffset % 8;
septets[byteOffset] |= c << shift;
if (shift > 1) {
septets[byteOffset + 1] = (byte) (c >> (8 - shift));
}
bitOffset += 7;
if (c == GsmAlphabet.GSM_EXTENDED_ESCAPE) {
// double Escape maps to space character
defaultTable.append(' ');
turkishTable.append(' ');
portugueseTable.append(' ');
} else {
// other unmapped chars map to the default or locking shift table
defaultTable.append(sBasicTables[0].charAt(c));
turkishTable.append(sBasicTables[1].charAt(c));
portugueseTable.append(sBasicTables[3].charAt(c));
}
}
String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0,
0, language);
assertEquals(defaultTable.toString(), decoded);
decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0, 1, language);
assertEquals(turkishTable.toString(), decoded);
decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0, 3, language);
assertEquals(portugueseTable.toString(), decoded);
}
}
}

View File

@@ -19,21 +19,57 @@ package com.android.internal.telephony;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import java.util.Random;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER;
/**
* Test cases to verify selection of the optimal 7 bit encoding tables
* (for all combinations of enabled national language tables) for messages
* containing Turkish, Spanish, Portuguese, Greek, and other symbols
* present in the GSM default and national language tables defined in
* 3GPP TS 23.038. Also verifies correct SMS encoding for CDMA, which only
* supports the GSM 7 bit default alphabet, ASCII 8 bit, and UCS-2.
* Tests both encoding variations: unsupported characters mapped to space,
* and unsupported characters force entire message to UCS-2.
*/
public class SmsMessageBodyTest extends AndroidTestCase {
private static final String TAG = "SmsMessageBodyTest";
// ASCII chars in the GSM 7 bit default alphabet
private static final String sAsciiChars = "@$_ !\"#%&'()*+,-./0123456789" +
":;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\r";
private static final String sGsmBasicChars = "\u00a3\u00a5\u00e8\u00e9" +
"\u00f9\u00ec\u00f2\u00c7\u00d8\u00f8\u00c5\u00e5\u0394\u03a6" +
"\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u00c6\u00e6" +
"\u00df\u00c9\u00a4\u00a1\u00c4\u00d6\u00d1\u00dc\u00a7\u00bf" +
"\u00e4\u00f6\u00f1\u00fc\u00e0";
private static final String sGsmExtendedAsciiChars = "{|}\\[~]^\f";
// Unicode chars in the GSM 7 bit default alphabet and both locking shift tables
private static final String sGsmDefaultChars = "\u00a3\u00a5\u00e9\u00c7\u0394\u00c9" +
"\u00dc\u00a7\u00fc\u00e0";
// Unicode chars in the GSM 7 bit default table and Turkish locking shift tables
private static final String sGsmDefaultAndTurkishTables = "\u00f9\u00f2\u00c5\u00e5\u00df" +
"\u00a4\u00c4\u00d6\u00d1\u00e4\u00f6\u00f1";
// Unicode chars in the GSM 7 bit default table but not the locking shift tables
private static final String sGsmDefaultTableOnly = "\u00e8\u00ec\u00d8\u00f8\u00c6\u00e6" +
"\u00a1\u00bf";
// ASCII chars in the GSM default extension table
private static final String sGsmExtendedAsciiChars = "{}[]\f";
// chars in GSM default extension table and Portuguese locking shift table
private static final String sGsmExtendedPortugueseLocking = "^\\|~";
// Euro currency symbol
private static final String sGsmExtendedEuroSymbol = "\u20ac";
// CJK ideographs, Hiragana, Katakana, full width letters, Cyrillic, etc.
private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" +
"\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" +
"\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" +
@@ -43,6 +79,86 @@ public class SmsMessageBodyTest extends AndroidTestCase {
"\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" +
"\u00a2\u00a9\u00ae\u2122";
// chars in Turkish single shift and locking shift tables
private static final String sTurkishChars = "\u0131\u011e\u011f\u015e\u015f\u0130";
// chars in Spanish single shift table and Portuguese single and locking shift tables
private static final String sPortugueseAndSpanishChars = "\u00c1\u00e1\u00cd\u00ed"
+ "\u00d3\u00f3\u00da\u00fa";
// chars in all national language tables but not in the standard GSM alphabets
private static final String sNationalLanguageTablesOnly = "\u00e7";
// chars in Portuguese single shift and locking shift tables
private static final String sPortugueseChars = "\u00ea\u00d4\u00f4\u00c0\u00c2\u00e2"
+ "\u00ca\u00c3\u00d5\u00e3\u00f5";
// chars in Portuguese locking shift table only
private static final String sPortugueseLockingShiftChars = "\u00aa\u221e\u00ba`";
// Greek letters in GSM alphabet missing from Portuguese locking and single shift tables
private static final String sGreekLettersNotInPortugueseTables = "\u039b\u039e";
// Greek letters in GSM alphabet and Portuguese single shift (but not locking shift) table
private static final String sGreekLettersInPortugueseShiftTable =
"\u03a6\u0393\u03a9\u03a0\u03a8\u03a3\u0398";
// List of classes of characters in SMS tables
private static final String[] sCharacterClasses = {
sGsmExtendedAsciiChars,
sGsmExtendedPortugueseLocking,
sGsmDefaultChars,
sGsmDefaultAndTurkishTables,
sGsmDefaultTableOnly,
sGsmExtendedEuroSymbol,
sUnicodeChars,
sTurkishChars,
sPortugueseChars,
sPortugueseLockingShiftChars,
sPortugueseAndSpanishChars,
sGreekLettersNotInPortugueseTables,
sGreekLettersInPortugueseShiftTable,
sNationalLanguageTablesOnly,
sAsciiChars
};
private static final int sNumCharacterClasses = sCharacterClasses.length;
// For each character class, whether it is present in a particular char table.
// First three entries are locking shift tables, followed by four single shift tables
private static final boolean[][] sCharClassPresenceInTables = {
// ASCII chars in all GSM extension tables
{false, false, false, true, true, true, true},
// ASCII chars in all GSM extension tables and Portuguese locking shift table
{false, false, true, true, true, true, true},
// non-ASCII chars in GSM default alphabet and all locking tables
{true, true, true, false, false, false, false},
// non-ASCII chars in GSM default alphabet and Turkish locking shift table
{true, true, false, false, false, false, false},
// non-ASCII chars in GSM default alphabet table only
{true, false, false, false, false, false, false},
// Euro symbol is present in several tables
{false, true, true, true, true, true, true},
// Unicode characters not present in any 7 bit tables
{false, false, false, false, false, false, false},
// Characters specific to Turkish language
{false, true, false, false, true, false, false},
// Characters in Portuguese single shift and locking shift tables
{false, false, true, false, false, false, true},
// Characters in Portuguese locking shift table only
{false, false, true, false, false, false, false},
// Chars in Spanish single shift and Portuguese single and locking shift tables
{false, false, true, false, false, true, true},
// Greek letters in GSM default alphabet missing from Portuguese tables
{true, true, false, false, false, false, false},
// Greek letters in GSM alphabet and Portuguese single shift table
{true, true, false, false, false, false, true},
// Chars in all national language tables but not the standard GSM tables
{false, true, true, false, true, true, true},
// ASCII chars in GSM default alphabet
{true, true, true, false, false, false, false}
};
private static final int sTestLengthCount = 12;
private static final int[] sSeptetTestLengths =
@@ -60,11 +176,92 @@ public class SmsMessageBodyTest extends AndroidTestCase {
private static final int[] sUnicodeUnitsRemaining =
{ 70, 69, 68, 35, 1, 0, 63, 34, 1, 0, 66, 41};
// Combinations of enabled GSM national language single shift tables
private static final int[][] sEnabledSingleShiftTables = {
{}, // GSM default alphabet only
{1}, // Turkish (single shift only)
{1}, // Turkish (single and locking shift)
{2}, // Spanish
{3}, // Portuguese (single shift only)
{3}, // Portuguese (single and locking shift)
{1, 2}, // Turkish + Spanish (single shift only)
{1, 2}, // Turkish + Spanish (single and locking shift)
{1, 3}, // Turkish + Portuguese (single shift only)
{1, 3}, // Turkish + Portuguese (single and locking shift)
{2, 3}, // Spanish + Portuguese (single shift only)
{2, 3}, // Spanish + Portuguese (single and locking shift)
{1, 2, 3}, // Turkish, Spanish, Portuguese (single shift only)
{1, 2, 3}, // Turkish, Spanish, Portuguese (single and locking shift)
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} // all language tables
};
// Combinations of enabled GSM national language locking shift tables
private static final int[][] sEnabledLockingShiftTables = {
{}, // GSM default alphabet only
{}, // Turkish (single shift only)
{1}, // Turkish (single and locking shift)
{}, // Spanish (no locking shift table)
{}, // Portuguese (single shift only)
{3}, // Portuguese (single and locking shift)
{}, // Turkish + Spanish (single shift only)
{1}, // Turkish + Spanish (single and locking shift)
{}, // Turkish + Portuguese (single shift only)
{1, 3}, // Turkish + Portuguese (single and locking shift)
{}, // Spanish + Portuguese (single shift only)
{3}, // Spanish + Portuguese (single and locking shift)
{}, // Turkish, Spanish, Portuguese (single shift only)
{1, 3}, // Turkish, Spanish, Portuguese (single and locking shift)
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} // all language tables
};
// LanguagePair counter indexes to check for each entry above
private static final int[][] sLanguagePairIndexesByEnabledIndex = {
{0}, // default tables only
{0, 1}, // Turkish (single shift only)
{0, 1, 4, 5}, // Turkish (single and locking shift)
{0, 2}, // Spanish
{0, 3}, // Portuguese (single shift only)
{0, 3, 8, 11}, // Portuguese (single and locking shift)
{0, 1, 2}, // Turkish + Spanish (single shift only)
{0, 1, 2, 4, 5, 6}, // Turkish + Spanish (single and locking shift)
{0, 1, 3}, // Turkish + Portuguese (single shift only)
{0, 1, 3, 4, 5, 7, 8, 9, 11}, // Turkish + Portuguese (single and locking shift)
{0, 2, 3}, // Spanish + Portuguese (single shift only)
{0, 2, 3, 8, 10, 11}, // Spanish + Portuguese (single and locking shift)
{0, 1, 2, 3}, // all languages (single shift only)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, // all languages (single and locking shift)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} // all languages (no Indic chars in test)
};
/**
* User data header requires one octet for length. Count as one septet, because
* all combinations of header elements below will have at least one free bit
* when padding to the nearest septet boundary.
*/
private static final int UDH_SEPTET_COST_LENGTH = 1;
/**
* Using a non-default language locking shift table OR single shift table
* requires a user data header of 3 octets, or 4 septets, plus UDH length.
*/
private static final int UDH_SEPTET_COST_ONE_SHIFT_TABLE = 4;
/**
* Using a non-default language locking shift table AND single shift table
* requires a user data header of 6 octets, or 7 septets, plus UDH length.
*/
private static final int UDH_SEPTET_COST_TWO_SHIFT_TABLES = 7;
/**
* Multi-part messages require a user data header of 5 octets, or 6 septets,
* plus UDH length.
*/
private static final int UDH_SEPTET_COST_CONCATENATED_MESSAGE = 6;
@SmallTest
public void testCalcLengthAscii() throws Exception {
StringBuilder sb = new StringBuilder(320);
int[] values = {0, 0, 0, SmsMessage.ENCODING_7BIT};
int[] values = {0, 0, 0, SmsMessage.ENCODING_7BIT, 0, 0};
int startPos = 0;
int asciiCharsLen = sAsciiChars.length();
@@ -93,21 +290,11 @@ public class SmsMessageBodyTest extends AndroidTestCase {
}
}
@SmallTest
public void testCalcLength7bitGsm() throws Exception {
// TODO
}
@SmallTest
public void testCalcLength7bitGsmExtended() throws Exception {
// TODO
}
@SmallTest
public void testCalcLengthUnicode() throws Exception {
StringBuilder sb = new StringBuilder(160);
int[] values = {0, 0, 0, SmsMessage.ENCODING_16BIT};
int[] values7bit = {1, 0, 0, SmsMessage.ENCODING_7BIT};
int[] values = {0, 0, 0, SmsMessage.ENCODING_16BIT, 0, 0};
int[] values7bit = {1, 0, 0, SmsMessage.ENCODING_7BIT, 0, 0};
int startPos = 0;
int unicodeCharsLen = sUnicodeChars.length();
@@ -139,6 +326,229 @@ public class SmsMessageBodyTest extends AndroidTestCase {
}
}
private static class LanguagePair {
// index is 2 for Portuguese locking shift because there is no Spanish locking shift table
private final int langTableIndex;
private final int langShiftTableIndex;
int length;
int missingChars7bit;
LanguagePair(int langTable, int langShiftTable) {
langTableIndex = langTable;
langShiftTableIndex = langShiftTable;
}
void clear() {
length = 0;
missingChars7bit = 0;
}
void addChar(boolean[] charClassTableRow) {
if (charClassTableRow[langTableIndex]) {
length++;
} else if (charClassTableRow[3 + langShiftTableIndex]) {
length += 2;
} else {
length++; // use ' ' for unmapped char in 7 bit only mode
missingChars7bit++;
}
}
}
private static class CounterHelper {
LanguagePair[] mCounters;
int[] mStatsCounters;
int mUnicodeCounter;
CounterHelper() {
mCounters = new LanguagePair[12];
mStatsCounters = new int[12];
for (int i = 0; i < 12; i++) {
mCounters[i] = new LanguagePair(i/4, i%4);
}
}
void clear() {
// Note: don't clear stats counters
for (int i = 0; i < 12; i++) {
mCounters[i].clear();
}
}
void addChar(int charClass) {
boolean[] charClassTableRow = sCharClassPresenceInTables[charClass];
for (int i = 0; i < 12; i++) {
mCounters[i].addChar(charClassTableRow);
}
}
void fillData(int enabledLangsIndex, boolean use7bitOnly, int[] values, int length) {
int[] languagePairs = sLanguagePairIndexesByEnabledIndex[enabledLangsIndex];
int minNumSeptets = Integer.MAX_VALUE;
int minNumSeptetsWithHeader = Integer.MAX_VALUE;
int minNumMissingChars = Integer.MAX_VALUE;
int langIndex = -1;
int langShiftIndex = -1;
for (int i : languagePairs) {
LanguagePair pair = mCounters[i];
int udhLength = 0;
if (i != 0) {
udhLength = UDH_SEPTET_COST_LENGTH;
if (i < 4 || i % 4 == 0) {
udhLength += UDH_SEPTET_COST_ONE_SHIFT_TABLE;
} else {
udhLength += UDH_SEPTET_COST_TWO_SHIFT_TABLES;
}
}
int numSeptetsWithHeader;
if (pair.length > (MAX_USER_DATA_SEPTETS - udhLength)) {
if (udhLength == 0) {
udhLength = UDH_SEPTET_COST_LENGTH;
}
udhLength += UDH_SEPTET_COST_CONCATENATED_MESSAGE;
int septetsPerPart = MAX_USER_DATA_SEPTETS - udhLength;
int msgCount = (pair.length + septetsPerPart - 1) / septetsPerPart;
numSeptetsWithHeader = udhLength * msgCount + pair.length;
} else {
numSeptetsWithHeader = udhLength + pair.length;
}
if (use7bitOnly) {
if (pair.missingChars7bit < minNumMissingChars || (pair.missingChars7bit ==
minNumMissingChars && numSeptetsWithHeader < minNumSeptetsWithHeader)) {
minNumSeptets = pair.length;
minNumSeptetsWithHeader = numSeptetsWithHeader;
minNumMissingChars = pair.missingChars7bit;
langIndex = pair.langTableIndex;
langShiftIndex = pair.langShiftTableIndex;
}
} else {
if (pair.missingChars7bit == 0 && numSeptetsWithHeader < minNumSeptetsWithHeader) {
minNumSeptets = pair.length;
minNumSeptetsWithHeader = numSeptetsWithHeader;
langIndex = pair.langTableIndex;
langShiftIndex = pair.langShiftTableIndex;
}
}
}
if (langIndex == -1) {
// nothing matches, use values for Unicode
int byteCount = length * 2;
if (byteCount > MAX_USER_DATA_BYTES) {
values[0] = (byteCount + MAX_USER_DATA_BYTES_WITH_HEADER - 1) /
MAX_USER_DATA_BYTES_WITH_HEADER;
values[2] = ((values[0] * MAX_USER_DATA_BYTES_WITH_HEADER) - byteCount) / 2;
} else {
values[0] = 1;
values[2] = (MAX_USER_DATA_BYTES - byteCount) / 2;
}
values[1] = length;
values[3] = SmsMessage.ENCODING_16BIT;
values[4] = 0;
values[5] = 0;
mUnicodeCounter++;
} else {
int udhLength = 0;
if (langIndex != 0 || langShiftIndex != 0) {
udhLength = UDH_SEPTET_COST_LENGTH;
if (langIndex == 0 || langShiftIndex == 0) {
udhLength += UDH_SEPTET_COST_ONE_SHIFT_TABLE;
} else {
udhLength += UDH_SEPTET_COST_TWO_SHIFT_TABLES;
}
}
int msgCount;
if (minNumSeptets > (MAX_USER_DATA_SEPTETS - udhLength)) {
if (udhLength == 0) {
udhLength = UDH_SEPTET_COST_LENGTH;
}
udhLength += UDH_SEPTET_COST_CONCATENATED_MESSAGE;
int septetsPerPart = MAX_USER_DATA_SEPTETS - udhLength;
msgCount = (minNumSeptets + septetsPerPart - 1) / septetsPerPart;
} else {
msgCount = 1;
}
values[0] = msgCount;
values[1] = minNumSeptets;
values[2] = (values[0] * (MAX_USER_DATA_SEPTETS - udhLength)) - minNumSeptets;
values[3] = SmsMessage.ENCODING_7BIT;
values[4] = (langIndex == 2 ? 3 : langIndex); // Portuguese is code 3, index 2
values[5] = langShiftIndex;
assertEquals("minNumSeptetsWithHeader", minNumSeptetsWithHeader,
udhLength * msgCount + minNumSeptets);
mStatsCounters[langIndex * 4 + langShiftIndex]++;
}
}
void printStats() {
Log.d(TAG, "Unicode selection count: " + mUnicodeCounter);
for (int i = 0; i < 12; i++) {
Log.d(TAG, "Language pair index " + i + " count: " + mStatsCounters[i]);
}
}
}
@LargeTest
public void testCalcLengthMixed7bit() throws Exception {
StringBuilder sb = new StringBuilder(320);
CounterHelper ch = new CounterHelper();
Random r = new Random(0x4321); // use the same seed for reproducibility
int[] expectedValues = new int[6];
int[] origLockingShiftTables = GsmAlphabet.getEnabledLockingShiftTables();
int[] origSingleShiftTables = GsmAlphabet.getEnabledSingleShiftTables();
int enabledLanguagesTestCases = sEnabledSingleShiftTables.length;
long startTime = System.currentTimeMillis();
// Repeat for 10 test runs
for (int run = 0; run < 10; run++) {
sb.setLength(0);
ch.clear();
int unicodeOnlyCount = 0;
// Test incrementally from 1 to 320 character random messages
for (int i = 1; i < 320; i++) {
// 1% chance to add from each special character class, else add an ASCII char
int charClass = r.nextInt(100);
if (charClass >= sNumCharacterClasses) {
charClass = sNumCharacterClasses - 1; // last class is ASCII
}
int classLength = sCharacterClasses[charClass].length();
char nextChar = sCharacterClasses[charClass].charAt(r.nextInt(classLength));
sb.append(nextChar);
ch.addChar(charClass);
// if (i % 20 == 0) {
// Log.d(TAG, "test string: " + sb);
// }
// Test string against all combinations of enabled languages
boolean unicodeOnly = true;
for (int j = 0; j < enabledLanguagesTestCases; j++) {
GsmAlphabet.setEnabledSingleShiftTables(sEnabledSingleShiftTables[j]);
GsmAlphabet.setEnabledLockingShiftTables(sEnabledLockingShiftTables[j]);
ch.fillData(j, false, expectedValues, i);
if (expectedValues[3] == SmsMessage.ENCODING_7BIT) {
unicodeOnly = false;
}
callGsmLengthMethods(sb, false, expectedValues);
// test 7 bit only mode
ch.fillData(j, true, expectedValues, i);
callGsmLengthMethods(sb, true, expectedValues);
}
// after 10 iterations with a Unicode-only string, skip to next test string
// so we can spend more time testing strings that do encode into 7 bits.
if (unicodeOnly && ++unicodeOnlyCount == 10) {
// Log.d(TAG, "Unicode only: skipping to next test string");
break;
}
}
}
ch.printStats();
Log.d(TAG, "Completed in " + (System.currentTimeMillis() - startTime) + " ms");
GsmAlphabet.setEnabledLockingShiftTables(origLockingShiftTables);
GsmAlphabet.setEnabledSingleShiftTables(origSingleShiftTables);
}
private void callGsmLengthMethods(CharSequence msgBody, boolean use7bitOnly,
int[] expectedValues)
{
@@ -164,6 +574,8 @@ public class SmsMessageBodyTest extends AndroidTestCase {
assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount);
assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining);
assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize);
assertEquals("languageTable", expectedValues[4], ted.languageTable);
assertEquals("languageShiftTable", expectedValues[5], ted.languageShiftTable);
}
private void callCdmaLengthMethods(CharSequence msgBody, boolean use7bitOnly,