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
testAddressing()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
testUdh()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
testUcs2()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
testMultipart()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
testCPHSVoiceMail()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
testCingularVoiceMail()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
testEmailGateway()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
testExtendedCharacterTable()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
testFragmentText()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
testFragmentTurkishText()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
testDecode()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 
decodeSingle(int language)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
testDecodeExtended()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
testDecodeExtendedFallback()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