summaryrefslogtreecommitdiffstats
path: root/telephony/tests/telephonytests/src
diff options
context:
space:
mode:
authorJake Hamby <jhamby@google.com>2011-05-05 13:31:03 -0700
committerAndroid Git Automerger <android-git-automerger@android.com>2011-05-05 13:31:03 -0700
commit18cc814c94edb188b73a92f0d34878d9dc054ff6 (patch)
tree9e68e5068c56cc4d4ce74a2a69a4ccc6a7dd6b9e /telephony/tests/telephonytests/src
parent8a14d8b9c03c611cabde5743cfdcc9ff51314550 (diff)
parent4d53cb02168fe35104d7e644dd9b3efd8ca4c91b (diff)
downloadframeworks_base-18cc814c94edb188b73a92f0d34878d9dc054ff6.zip
frameworks_base-18cc814c94edb188b73a92f0d34878d9dc054ff6.tar.gz
frameworks_base-18cc814c94edb188b73a92f0d34878d9dc054ff6.tar.bz2
am 4d53cb02: Merge "Enable support for SMS national language shift tables." into gingerbread
* commit '4d53cb02168fe35104d7e644dd9b3efd8ca4c91b': Enable support for SMS national language shift tables.
Diffstat (limited to 'telephony/tests/telephonytests/src')
-rw-r--r--telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java61
-rw-r--r--telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java195
-rw-r--r--telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java450
3 files changed, 662 insertions, 44 deletions
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
index 3a9c511..e83a822 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmAlphabetTest.java
@@ -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));
}
}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index 3103fc1..41a719e 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -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);
+ }
+ }
}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
index b214887..170bd9b 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
@@ -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();
@@ -94,20 +291,10 @@ 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,