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