Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony;
     18 
     19 import android.telephony.TelephonyManager;
     20 import android.test.AndroidTestCase;
     21 import android.test.suitebuilder.annotation.SmallTest;
     22 
     23 import com.android.internal.telephony.gsm.SmsMessage;
     24 import com.android.internal.util.HexDump;
     25 
     26 import java.util.ArrayList;
     27 
     28 public class GsmSmsTest extends AndroidTestCase {
     29 
     30     @SmallTest
     31     public void testAddressing() throws Exception {
     32         String pdu = "07914151551512f2040B916105551511f100006060605130308A04D4F29C0E";
     33         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
     34         assertEquals("+14155551212", sms.getServiceCenterAddress());
     35         assertEquals("+16505551111", sms.getOriginatingAddress());
     36         assertEquals("Test", sms.getMessageBody());
     37 
     38         pdu = "07914151551512f2040B916105551511f100036060924180008A0DA"
     39                 + "8695DAC2E8FE9296A794E07";
     40         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
     41         assertEquals("+14155551212", sms.getServiceCenterAddress());
     42         assertEquals("+16505551111", sms.getOriginatingAddress());
     43         assertEquals("(Subject)Test", sms.getMessageBody());
     44 
     45         pdu = "07914151551512F20409E1BADCBE5AF100006060605130308A04D4F29C0E";
     46         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
     47         assertEquals("*#abc#*51", sms.getOriginatingAddress());
     48     }
     49 
     50     @SmallTest
     51     public void testUdh() throws Exception {
     52         String pdu = "07914140279510F6440A8111110301003BF56080207130138A8C0B05040B8423F"
     53                 + "000032A02010106276170706C69636174696F6E2F766E642E7761702E6D6D732D"
     54                 + "6D65737361676500AF848D0185B4848C8298524E453955304A6D7135514141426"
     55                 + "66C414141414D7741414236514141414141008D908918802B3135313232393737"
     56                 + "3638332F545950453D504C4D4E008A808E022B918805810306977F83687474703"
     57                 + "A2F2F36";
     58         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
     59         SmsHeader header = sms.getUserDataHeader();
     60         assertNotNull(header);
     61         assertNotNull(header.concatRef);
     62         assertEquals(header.concatRef.refNumber, 42);
     63         assertEquals(header.concatRef.msgCount, 2);
     64         assertEquals(header.concatRef.seqNumber, 1);
     65         assertEquals(header.concatRef.isEightBits, true);
     66         assertNotNull(header.portAddrs);
     67         assertEquals(header.portAddrs.destPort, 2948);
     68         assertEquals(header.portAddrs.origPort, 9200);
     69         assertEquals(header.portAddrs.areEightBits, false);
     70 
     71         pdu = "07914140279510F6440A8111110301003BF56080207130238A3B0B05040B8423F"
     72                 + "000032A0202362E3130322E3137312E3135302F524E453955304A6D7135514141"
     73                 + "42666C414141414D774141423651414141414100";
     74         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
     75         header = sms.getUserDataHeader();
     76         assertNotNull(header);
     77         assertNotNull(header.concatRef);
     78         assertEquals(header.concatRef.refNumber, 42);
     79         assertEquals(header.concatRef.msgCount, 2);
     80         assertEquals(header.concatRef.seqNumber, 2);
     81         assertEquals(header.concatRef.isEightBits, true);
     82         assertNotNull(header.portAddrs);
     83         assertEquals(header.portAddrs.destPort, 2948);
     84         assertEquals(header.portAddrs.origPort, 9200);
     85         assertEquals(header.portAddrs.areEightBits, false);
     86     }
     87 
     88     @SmallTest
     89     public void testUcs2() throws Exception {
     90         String pdu = "07912160130300F4040B914151245584F600087010807121352B1021220"
     91                 + "0A900AE00680065006C006C006F";
     92         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
     93         assertEquals("\u2122\u00a9\u00aehello", sms.getMessageBody());
     94     }
     95 
     96     @SmallTest
     97     public void testMultipart() throws Exception {
     98         /*
     99          * Multi-part text SMS with septet data.
    100          */
    101         String pdu = "07916163838408F6440B816105224431F700007060217175830AA0050003"
    102                 + "00020162B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
    103                 + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
    104                 + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
    105                 + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562B1582C"
    106                 + "168BC562B1582C168BC562B1582C168BC562B1582C168BC562";
    107         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    108         assertEquals(sms.getMessageBody(),
    109                 "1111111111111111111111111111111111111111"
    110                 + "1111111111111111111111111111111111111111"
    111                 + "1111111111111111111111111111111111111111"
    112                 + "111111111111111111111111111111111");
    113 
    114         pdu = "07916163838408F6440B816105224431F700007060217185000A23050003"
    115                 + "00020262B1582C168BC96432994C2693C96432994C2693C96432990C";
    116         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    117         assertEquals("1111111222222222222222222222", sms.getMessageBody());
    118     }
    119 
    120     @SmallTest
    121     public void testCPHSVoiceMail() throws Exception {
    122         // "set MWI flag"
    123 
    124         String pdu = "07912160130310F20404D0110041006060627171118A0120";
    125 
    126         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    127 
    128         assertTrue(sms.isReplace());
    129         assertEquals("_@", sms.getOriginatingAddress());
    130         assertEquals(" ", sms.getMessageBody());
    131         assertTrue(sms.isMWISetMessage());
    132 
    133         // "clear mwi flag"
    134 
    135         pdu = "07912160130310F20404D0100041006021924193352B0120";
    136 
    137         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    138 
    139         assertTrue(sms.isMWIClearMessage());
    140 
    141         // "clear MWI flag"
    142 
    143         pdu = "07912160130310F20404D0100041006060627161058A0120";
    144 
    145         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    146 
    147         assertTrue(sms.isReplace());
    148         assertEquals("\u0394@", sms.getOriginatingAddress());
    149         assertEquals(" ", sms.getMessageBody());
    150         assertTrue(sms.isMWIClearMessage());
    151     }
    152 
    153     @SmallTest
    154     public void testCingularVoiceMail() throws Exception {
    155         // "set MWI flag"
    156 
    157         String pdu = "07912180958750F84401800500C87020026195702B06040102000200";
    158         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    159 
    160         assertTrue(sms.isMWISetMessage());
    161         assertTrue(sms.isMwiDontStore());
    162 
    163         // "clear mwi flag"
    164 
    165         pdu = "07912180958750F84401800500C07020027160112B06040102000000";
    166         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    167 
    168         assertTrue(sms.isMWIClearMessage());
    169         assertTrue(sms.isMwiDontStore());
    170     }
    171 
    172     @SmallTest
    173     public void testEmailGateway() throws Exception {
    174         String pdu = "07914151551512f204038105f300007011103164638a28e6f71b50c687db" +
    175                 "7076d9357eb7412f7a794e07cdeb6275794c07bde8e5391d247e93f3";
    176 
    177         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    178 
    179         assertEquals("+14155551212", sms.getServiceCenterAddress());
    180         assertTrue(sms.isEmail());
    181         assertEquals("foo (at) example.com", sms.getEmailFrom());
    182         assertEquals("foo (at) example.com", sms.getDisplayOriginatingAddress());
    183         // As of https://android-git.corp.google.com/g/#change,9324
    184         // getPseudoSubject will always be empty, and any subject is not extracted.
    185         assertEquals("", sms.getPseudoSubject());
    186         assertEquals("test subject /test body", sms.getDisplayMessageBody());
    187         assertEquals("test subject /test body", sms.getEmailBody());
    188 
    189         // email gateway sms test, including gsm extended character set.
    190         pdu = "07914151551512f204038105f400007011103105458a29e6f71b50c687db" +
    191                 "7076d9357eb741af0d0a442fcfe9c23739bfe16d289bdee6b5f1813629";
    192 
    193         sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    194 
    195         assertEquals("+14155551212", sms.getServiceCenterAddress());
    196         assertTrue(sms.isEmail());
    197         assertEquals("foo (at) example.com", sms.getDisplayOriginatingAddress());
    198         assertEquals("foo (at) example.com", sms.getEmailFrom());
    199         assertEquals("{ testBody[^~\\] }", sms.getDisplayMessageBody());
    200         assertEquals("{ testBody[^~\\] }", sms.getEmailBody());
    201     }
    202 
    203     @SmallTest
    204     public void testExtendedCharacterTable() throws Exception {
    205         String pdu = "07914151551512f2040B916105551511f100006080615131728A44D4F29C0E2" +
    206                 "AE3E96537B94C068DD16179784C2FCB41F4B0985D06B958ADD00FB0E94536AF9749" +
    207                 "74DA6D281BA00E95E26D509B946FC3DBF87A25D56A04";
    208 
    209         SmsMessage sms = SmsMessage.createFromPdu(HexDump.hexStringToByteArray(pdu));
    210 
    211         assertEquals("+14155551212", sms.getServiceCenterAddress());
    212         assertEquals("+16505551111", sms.getOriginatingAddress());
    213         assertEquals("Test extended character table .,-!?@~_\\/&\"';^|:()<{}>[]=%*+#",
    214                 sms.getMessageBody());
    215     }
    216 
    217     // GSM 7 bit tables in String form, Escape (0x1B) replaced with '@'
    218     private static final String[] sBasicTables = {
    219         // GSM 7 bit default alphabet
    220         "@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5\u0394_"
    221             + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e@\u00c6\u00e6\u00df\u00c9"
    222             + " !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6"
    223             + "\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
    224 
    225         // Turkish locking shift table
    226         "@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5\u0394_"
    227             + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e@\u015e\u015f\u00df\u00c9"
    228             + " !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u0130ABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c4\u00d6"
    229             + "\u00d1\u00dc\u00a7\u00e7abcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
    230 
    231         // no locking shift table defined for Spanish
    232         "",
    233 
    234         // Portuguese locking shift table
    235         "@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1\u0394_"
    236             + "\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|@\u00c2\u00e2\u00ca\u00c9 !\"#\u00ba%&'()"
    237             + "*+,-./0123456789:;<=>?\u00cdABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc"
    238             + "\u00a7~abcdefghijklmnopqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0"
    239     };
    240 
    241     @SmallTest
    242     public void testFragmentText() throws Exception {
    243         boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
    244                 TelephonyManager.PHONE_TYPE_GSM);
    245 
    246         // Valid 160 character 7-bit text.
    247         String text = "123456789012345678901234567890123456789012345678901234567890" +
    248                 "1234567890123456789012345678901234567890123456789012345678901234567890" +
    249                 "123456789012345678901234567890";
    250         GsmAlphabet.TextEncodingDetails ted = SmsMessage.calculateLength(text, false);
    251         assertEquals(1, ted.msgCount);
    252         assertEquals(160, ted.codeUnitCount);
    253         assertEquals(1, ted.codeUnitSize);
    254         assertEquals(0, ted.languageTable);
    255         assertEquals(0, ted.languageShiftTable);
    256         if (isGsmPhone) {
    257             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
    258             assertEquals(1, fragments.size());
    259         }
    260 
    261         // Valid 161 character 7-bit text.
    262         text = "123456789012345678901234567890123456789012345678901234567890" +
    263                 "1234567890123456789012345678901234567890123456789012345678901234567890" +
    264                 "1234567890123456789012345678901";
    265         ted = SmsMessage.calculateLength(text, false);
    266         assertEquals(2, ted.msgCount);
    267         assertEquals(161, ted.codeUnitCount);
    268         assertEquals(1, ted.codeUnitSize);
    269         assertEquals(0, ted.languageTable);
    270         assertEquals(0, ted.languageShiftTable);
    271         if (isGsmPhone) {
    272             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
    273             assertEquals(2, fragments.size());
    274             assertEquals(text, fragments.get(0) + fragments.get(1));
    275             assertEquals(153, fragments.get(0).length());
    276             assertEquals(8, fragments.get(1).length());
    277         }
    278     }
    279 
    280     @SmallTest
    281     public void testFragmentTurkishText() throws Exception {
    282         boolean isGsmPhone = (TelephonyManager.getDefault().getPhoneType() ==
    283                 TelephonyManager.PHONE_TYPE_GSM);
    284 
    285         int[] oldTables = GsmAlphabet.getEnabledSingleShiftTables();
    286         int[] turkishTable = { 1 };
    287         GsmAlphabet.setEnabledSingleShiftTables(turkishTable);
    288 
    289         // Valid 77 character text with Turkish characters.
    290         String text = "" +
    291                 "";
    292         GsmAlphabet.TextEncodingDetails ted = SmsMessage.calculateLength(text, false);
    293         assertEquals(1, ted.msgCount);
    294         assertEquals(154, ted.codeUnitCount);
    295         assertEquals(1, ted.codeUnitSize);
    296         assertEquals(0, ted.languageTable);
    297         assertEquals(1, ted.languageShiftTable);
    298         if (isGsmPhone) {
    299             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
    300             assertEquals(1, fragments.size());
    301             assertEquals(text, fragments.get(0));
    302             assertEquals(77, fragments.get(0).length());
    303         }
    304 
    305         // Valid 78 character text with Turkish characters.
    306         text = "" +
    307                 "";
    308         ted = SmsMessage.calculateLength(text, false);
    309         assertEquals(2, ted.msgCount);
    310         assertEquals(156, ted.codeUnitCount);
    311         assertEquals(1, ted.codeUnitSize);
    312         assertEquals(0, ted.languageTable);
    313         assertEquals(1, ted.languageShiftTable);
    314         if (isGsmPhone) {
    315             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
    316             assertEquals(2, fragments.size());
    317             assertEquals(text, fragments.get(0) + fragments.get(1));
    318             assertEquals(74, fragments.get(0).length());
    319             assertEquals(4, fragments.get(1).length());
    320         }
    321 
    322         // Valid 160 character text with Turkish characters.
    323         text = "" +
    324                 "" +
    325                 "";
    326         ted = SmsMessage.calculateLength(text, false);
    327         assertEquals(3, ted.msgCount);
    328         assertEquals(320, ted.codeUnitCount);
    329         assertEquals(1, ted.codeUnitSize);
    330         assertEquals(0, ted.languageTable);
    331         assertEquals(1, ted.languageShiftTable);
    332         if (isGsmPhone) {
    333             ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text);
    334             assertEquals(3, fragments.size());
    335             assertEquals(text, fragments.get(0) + fragments.get(1) + fragments.get(2));
    336             assertEquals(74, fragments.get(0).length());
    337             assertEquals(74, fragments.get(1).length());
    338             assertEquals(12, fragments.get(2).length());
    339         }
    340 
    341         GsmAlphabet.setEnabledSingleShiftTables(oldTables);
    342     }
    343 
    344 
    345     @SmallTest
    346     public void testDecode() throws Exception {
    347         decodeSingle(0);    // default table
    348         decodeSingle(1);    // Turkish locking shift table
    349         decodeSingle(3);    // Portuguese locking shift table
    350     }
    351 
    352     private void decodeSingle(int language) throws Exception {
    353         byte[] septets = new byte[(7 * 128 + 7) / 8];
    354 
    355         int bitOffset = 0;
    356 
    357         for (int i = 0; i < 128; i++) {
    358             int v;
    359             if (i == 0x1b) {
    360                 // extended escape char
    361                 v = 0;
    362             } else {
    363                 v = i;
    364             }
    365 
    366             int byteOffset = bitOffset / 8;
    367             int shift = bitOffset % 8;
    368 
    369             septets[byteOffset] |= v << shift;
    370 
    371             if (shift > 1) {
    372                 septets[byteOffset + 1] = (byte) (v >> (8 - shift));
    373             }
    374 
    375             bitOffset += 7;
    376         }
    377 
    378         String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, 128, 0, language, 0);
    379         byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded, language, 0);
    380 
    381         assertEquals(sBasicTables[language], decoded);
    382 
    383         // reEncoded has the count septets byte at the front
    384         assertEquals(septets.length + 1, reEncoded.length);
    385 
    386         for (int i = 0; i < septets.length; i++) {
    387             assertEquals(septets[i], reEncoded[i + 1]);
    388         }
    389     }
    390 
    391     private static final int GSM_ESCAPE_CHARACTER = 0x1b;
    392 
    393     private static final String[] sExtendedTables = {
    394         // GSM 7 bit default alphabet extension table
    395         "\f^{}\\[~]|\u20ac",
    396 
    397         // Turkish single shift extension table
    398         "\f^{}\\[~]|\u011e\u0130\u015e\u00e7\u20ac\u011f\u0131\u015f",
    399 
    400         // Spanish single shift extension table
    401         "\u00e7\f^{}\\[~]|\u00c1\u00cd\u00d3\u00da\u00e1\u20ac\u00ed\u00f3\u00fa",
    402 
    403         // Portuguese single shift extension table
    404         "\u00ea\u00e7\f\u00d4\u00f4\u00c1\u00e1\u03a6\u0393^\u03a9\u03a0\u03a8\u03a3\u0398\u00ca"
    405             + "{}\\[~]|\u00c0\u00cd\u00d3\u00da\u00c3\u00d5\u00c2\u20ac\u00ed\u00f3\u00fa\u00e3"
    406             + "\u00f5\u00e2"
    407     };
    408 
    409     private static final int[][] sExtendedTableIndexes = {
    410         {0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x65},
    411         {0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x47, 0x49, 0x53, 0x63,
    412                 0x65, 0x67, 0x69, 0x73},
    413         {0x09, 0x0a, 0x14, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x49, 0x4f,
    414                 0x55, 0x61, 0x65, 0x69, 0x6f, 0x75},
    415         {0x05, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    416                 0x18, 0x19, 0x1f, 0x28, 0x29, 0x2f, 0x3c, 0x3d, 0x3e, 0x40, 0x41, 0x49,
    417                 0x4f, 0x55, 0x5b, 0x5c, 0x61, 0x65, 0x69, 0x6f, 0x75, 0x7b, 0x7c, 0x7f}
    418     };
    419 
    420     @SmallTest
    421     public void testDecodeExtended() throws Exception {
    422         for (int language = 0; language < 3; language++) {
    423             int[] tableIndex = sExtendedTableIndexes[language];
    424             int numSeptets = tableIndex.length * 2;  // two septets per extended char
    425             byte[] septets = new byte[(7 * numSeptets + 7) / 8];
    426 
    427             int bitOffset = 0;
    428 
    429             for (int v : tableIndex) {
    430                 // escape character
    431                 int byteOffset = bitOffset / 8;
    432                 int shift = bitOffset % 8;
    433 
    434                 septets[byteOffset] |= GSM_ESCAPE_CHARACTER << shift;
    435 
    436                 if (shift > 1) {
    437                     septets[byteOffset + 1] = (byte) (GSM_ESCAPE_CHARACTER >> (8 - shift));
    438                 }
    439 
    440                 bitOffset += 7;
    441 
    442                 // extended table index
    443                 byteOffset = bitOffset / 8;
    444                 shift = bitOffset % 8;
    445 
    446                 septets[byteOffset] |= v << shift;
    447 
    448                 if (shift > 1) {
    449                     septets[byteOffset + 1] = (byte) (v >> (8 - shift));
    450                 }
    451 
    452                 bitOffset += 7;
    453             }
    454 
    455             String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0,
    456                     0, language);
    457             byte[] reEncoded = GsmAlphabet.stringToGsm7BitPacked(decoded, 0, language);
    458 
    459             assertEquals(sExtendedTables[language], decoded);
    460 
    461             // reEncoded has the count septets byte at the front
    462             assertEquals(septets.length + 1, reEncoded.length);
    463 
    464             for (int i = 0; i < septets.length; i++) {
    465                 assertEquals(septets[i], reEncoded[i + 1]);
    466             }
    467         }
    468     }
    469 
    470     @SmallTest
    471     public void testDecodeExtendedFallback() throws Exception {
    472         // verify that unmapped characters in extension table fall back to locking shift table
    473         for (int language = 0; language < 3; language++) {
    474             int[] tableIndex = sExtendedTableIndexes[language];
    475             int numChars = 128 - tableIndex.length;
    476             int numSeptets = numChars * 2;  // two septets per extended char
    477             byte[] septets = new byte[(7 * numSeptets + 7) / 8];
    478 
    479             int tableOffset = 0;
    480             int bitOffset = 0;
    481 
    482             StringBuilder defaultTable = new StringBuilder(128);
    483             StringBuilder turkishTable = new StringBuilder(128);
    484             StringBuilder portugueseTable = new StringBuilder(128);
    485 
    486             for (char c = 0; c < 128; c++) {
    487                 // skip characters that are present in the current extension table
    488                 if (tableOffset < tableIndex.length && tableIndex[tableOffset] == c) {
    489                     tableOffset++;
    490                     continue;
    491                 }
    492 
    493                 // escape character
    494                 int byteOffset = bitOffset / 8;
    495                 int shift = bitOffset % 8;
    496 
    497                 septets[byteOffset] |= GSM_ESCAPE_CHARACTER << shift;
    498 
    499                 if (shift > 1) {
    500                     septets[byteOffset + 1] = (byte) (GSM_ESCAPE_CHARACTER >> (8 - shift));
    501                 }
    502 
    503                 bitOffset += 7;
    504 
    505                 // extended table index
    506                 byteOffset = bitOffset / 8;
    507                 shift = bitOffset % 8;
    508 
    509                 septets[byteOffset] |= c << shift;
    510 
    511                 if (shift > 1) {
    512                     septets[byteOffset + 1] = (byte) (c >> (8 - shift));
    513                 }
    514 
    515                 bitOffset += 7;
    516 
    517                 if (c == GsmAlphabet.GSM_EXTENDED_ESCAPE) {
    518                     // double Escape maps to space character
    519                     defaultTable.append(' ');
    520                     turkishTable.append(' ');
    521                     portugueseTable.append(' ');
    522                 } else {
    523                     // other unmapped chars map to the default or locking shift table
    524                     defaultTable.append(sBasicTables[0].charAt(c));
    525                     turkishTable.append(sBasicTables[1].charAt(c));
    526                     portugueseTable.append(sBasicTables[3].charAt(c));
    527                 }
    528             }
    529 
    530             String decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0,
    531                     0, language);
    532 
    533             assertEquals(defaultTable.toString(), decoded);
    534 
    535             decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0, 1, language);
    536             assertEquals(turkishTable.toString(), decoded);
    537 
    538             decoded = GsmAlphabet.gsm7BitPackedToString(septets, 0, numSeptets, 0, 3, language);
    539             assertEquals(portugueseTable.toString(), decoded);
    540         }
    541     }
    542 }
    543