GsmAlphabet.java revision b55df4471ed55a0e91dee79304f3b1209ffa4b35
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.text.TextUtils;
20import android.util.SparseIntArray;
21
22import android.util.Log;
23
24import java.nio.ByteBuffer;
25import java.nio.charset.Charset;
26
27/**
28 * This class implements the character set mapping between
29 * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
30 * and UTF-16
31 *
32 * {@hide}
33 */
34public class GsmAlphabet {
35    static final String LOG_TAG = "GSM";
36
37
38
39    //***** Constants
40
41    /**
42     * This escapes extended characters, and when present indicates that the
43     * following character should
44     * be looked up in the "extended" table
45     *
46     * gsmToChar(GSM_EXTENDED_ESCAPE) returns 0xffff
47     */
48
49    public static final byte GSM_EXTENDED_ESCAPE = 0x1B;
50
51
52    /**
53     * char to GSM alphabet char
54     * Returns ' ' in GSM alphabet if there's no possible match
55     * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table
56     * In this case, you must call charToGsmExtended() for the value that
57     * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string
58     */
59    public static int
60    charToGsm(char c) {
61        try {
62            return charToGsm(c, false);
63        } catch (EncodeException ex) {
64            // this should never happen
65            return sGsmSpaceChar;
66        }
67    }
68
69    /**
70     * char to GSM alphabet char
71     * @param throwException If true, throws EncodeException on invalid char.
72     *   If false, returns GSM alphabet ' ' char.
73     *
74     * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table
75     * In this case, you must call charToGsmExtended() for the value that
76     * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string
77     */
78
79    public static int
80    charToGsm(char c, boolean throwException) throws EncodeException {
81        int ret;
82
83        ret = charToGsm.get(c, -1);
84
85        if (ret == -1) {
86            ret = charToGsmExtended.get(c, -1);
87
88            if (ret == -1) {
89                if (throwException) {
90                    throw new EncodeException(c);
91                } else {
92                    return sGsmSpaceChar;
93                }
94            } else {
95                return GSM_EXTENDED_ESCAPE;
96            }
97        }
98
99        return ret;
100
101    }
102
103
104    /**
105     * char to extended GSM alphabet char
106     *
107     * Extended chars should be escaped with GSM_EXTENDED_ESCAPE
108     *
109     * Returns ' ' in GSM alphabet if there's no possible match
110     *
111     */
112    public static int
113    charToGsmExtended(char c) {
114        int ret;
115
116        ret = charToGsmExtended.get(c, -1);
117
118        if (ret == -1) {
119            return sGsmSpaceChar;
120        }
121
122        return ret;
123    }
124
125    /**
126     * Converts a character in the GSM alphabet into a char
127     *
128     * if GSM_EXTENDED_ESCAPE is passed, 0xffff is returned. In this case,
129     * the following character in the stream should be decoded with
130     * gsmExtendedToChar()
131     *
132     * If an unmappable value is passed (one greater than 127), ' ' is returned
133     */
134
135    public static char
136    gsmToChar(int gsmChar) {
137        return (char)gsmToChar.get(gsmChar, ' ');
138    }
139
140    /**
141     * Converts a character in the extended GSM alphabet into a char
142     *
143     * if GSM_EXTENDED_ESCAPE is passed, ' ' is returned since no second
144     * extension page has yet been defined (see Note 1 in table 6.2.1.1 of
145     * TS 23.038 v7.00)
146     *
147     * If an unmappable value is passed , ' ' is returned
148     */
149
150    public static char
151    gsmExtendedToChar(int gsmChar) {
152        int ret;
153
154        ret = gsmExtendedToChar.get(gsmChar, -1);
155
156        if (ret == -1) {
157            return ' ';
158        }
159
160        return (char)ret;
161    }
162
163    /**
164     * Converts a String into a byte array containing the 7-bit packed
165     * GSM Alphabet representation of the string. If a header is provided,
166     * this is included in the returned byte array and padded to a septet
167     * boundary.
168     *
169     * Unencodable chars are encoded as spaces
170     *
171     * Byte 0 in the returned byte array is the count of septets used,
172     * including the header and header padding. The returned byte array is
173     * the minimum size required to store the packed septets. The returned
174     * array cannot contain more than 255 septets.
175     *
176     * @param data The text string to encode.
177     * @param header Optional header (including length byte) that precedes
178     * the encoded data, padded to septet boundary.
179     * @return Byte array containing header and encoded data.
180     */
181    public static byte[] stringToGsm7BitPackedWithHeader(String data, byte[] header)
182            throws EncodeException {
183
184        if (header == null || header.length == 0) {
185            return stringToGsm7BitPacked(data);
186        }
187
188        int headerBits = (header.length + 1) * 8;
189        int headerSeptets = (headerBits + 6) / 7;
190
191        byte[] ret = stringToGsm7BitPacked(data, headerSeptets, true);
192
193        // Paste in the header
194        ret[1] = (byte)header.length;
195        System.arraycopy(header, 0, ret, 2, header.length);
196        return ret;
197    }
198
199    /**
200     * Converts a String into a byte array containing
201     * the 7-bit packed GSM Alphabet representation of the string.
202     *
203     * Unencodable chars are encoded as spaces
204     *
205     * Byte 0 in the returned byte array is the count of septets used
206     * The returned byte array is the minimum size required to store
207     * the packed septets. The returned array cannot contain more than 255
208     * septets.
209     *
210     * @param data the data string to encode
211     * @throws EncodeException if String is too large to encode
212     */
213    public static byte[] stringToGsm7BitPacked(String data)
214            throws EncodeException {
215        return stringToGsm7BitPacked(data, 0, true);
216    }
217
218    /**
219     * Converts a String into a byte array containing
220     * the 7-bit packed GSM Alphabet representation of the string.
221     *
222     * Byte 0 in the returned byte array is the count of septets used
223     * The returned byte array is the minimum size required to store
224     * the packed septets. The returned array cannot contain more than 255
225     * septets.
226     *
227     * @param data the text to convert to septets
228     * @param startingSeptetOffset the number of padding septets to put before
229     *  the character data at the beginning of the array
230     * @param throwException If true, throws EncodeException on invalid char.
231     *   If false, replaces unencodable char with GSM alphabet space char.
232     *
233     * @throws EncodeException if String is too large to encode
234     */
235    public static byte[] stringToGsm7BitPacked(String data, int startingSeptetOffset,
236            boolean throwException) throws EncodeException {
237        int dataLen = data.length();
238        int septetCount = countGsmSeptets(data, throwException) + startingSeptetOffset;
239        if (septetCount > 255) {
240            throw new EncodeException("Payload cannot exceed 255 septets");
241        }
242        int byteCount = ((septetCount * 7) + 7) / 8;
243        byte[] ret = new byte[byteCount + 1];  // Include space for one byte length prefix.
244        for (int i = 0, septets = startingSeptetOffset, bitOffset = startingSeptetOffset * 7;
245                 i < dataLen && septets < septetCount;
246                 i++, bitOffset += 7) {
247            char c = data.charAt(i);
248            int v = GsmAlphabet.charToGsm(c, throwException);
249            if (v == GSM_EXTENDED_ESCAPE) {
250                v = GsmAlphabet.charToGsmExtended(c);  // Lookup the extended char.
251                packSmsChar(ret, bitOffset, GSM_EXTENDED_ESCAPE);
252                bitOffset += 7;
253                septets++;
254            }
255            packSmsChar(ret, bitOffset, v);
256            septets++;
257        }
258        ret[0] = (byte) (septetCount);  // Validated by check above.
259        return ret;
260    }
261
262    /**
263     * Pack a 7-bit char into its appropriate place in a byte array
264     *
265     * @param bitOffset the bit offset that the septet should be packed at
266     *                  (septet index * 7)
267     */
268    private static void
269    packSmsChar(byte[] packedChars, int bitOffset, int value) {
270        int byteOffset = bitOffset / 8;
271        int shift = bitOffset % 8;
272
273        packedChars[++byteOffset] |= value << shift;
274
275        if (shift > 1) {
276            packedChars[++byteOffset] = (byte)(value >> (8 - shift));
277        }
278    }
279
280    /**
281     * Convert a GSM alphabet 7 bit packed string (SMS string) into a
282     * {@link java.lang.String}.
283     *
284     * See TS 23.038 6.1.2.1 for SMS Character Packing
285     *
286     * @param pdu the raw data from the pdu
287     * @param offset the byte offset of
288     * @param lengthSeptets string length in septets, not bytes
289     * @return String representation or null on decoding exception
290     */
291    public static String gsm7BitPackedToString(byte[] pdu, int offset,
292            int lengthSeptets) {
293        return gsm7BitPackedToString(pdu, offset, lengthSeptets, 0);
294    }
295
296    /**
297     * Convert a GSM alphabet 7 bit packed string (SMS string) into a
298     * {@link java.lang.String}.
299     *
300     * See TS 23.038 6.1.2.1 for SMS Character Packing
301     *
302     * @param pdu the raw data from the pdu
303     * @param offset the byte offset of
304     * @param lengthSeptets string length in septets, not bytes
305     * @param numPaddingBits the number of padding bits before the start of the
306     *  string in the first byte
307     * @return String representation or null on decoding exception
308     */
309    public static String gsm7BitPackedToString(byte[] pdu, int offset,
310            int lengthSeptets, int numPaddingBits) {
311        StringBuilder ret = new StringBuilder(lengthSeptets);
312        boolean prevCharWasEscape;
313
314        try {
315            prevCharWasEscape = false;
316
317            for (int i = 0 ; i < lengthSeptets ; i++) {
318                int bitOffset = (7 * i) + numPaddingBits;
319
320                int byteOffset = bitOffset / 8;
321                int shift = bitOffset % 8;
322                int gsmVal;
323
324                gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
325
326                // if it crosses a byte boundary
327                if (shift > 1) {
328                    // set msb bits to 0
329                    gsmVal &= 0x7f >> (shift - 1);
330
331                    gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
332                }
333
334                if (prevCharWasEscape) {
335                    ret.append(GsmAlphabet.gsmExtendedToChar(gsmVal));
336                    prevCharWasEscape = false;
337                } else if (gsmVal == GSM_EXTENDED_ESCAPE) {
338                    prevCharWasEscape = true;
339                } else {
340                    ret.append(GsmAlphabet.gsmToChar(gsmVal));
341                }
342            }
343        } catch (RuntimeException ex) {
344            Log.e(LOG_TAG, "Error GSM 7 bit packed: ", ex);
345            return null;
346        }
347
348        return ret.toString();
349    }
350
351
352    /**
353     * Convert a GSM alphabet string that's stored in 8-bit unpacked
354     * format (as it often appears in SIM records) into a String
355     *
356     * Field may be padded with trailing 0xff's. The decode stops
357     * at the first 0xff encountered.
358     */
359    public static String
360    gsm8BitUnpackedToString(byte[] data, int offset, int length) {
361        return gsm8BitUnpackedToString(data, offset, length, "");
362    }
363
364    /**
365     * Convert a GSM alphabet string that's stored in 8-bit unpacked
366     * format (as it often appears in SIM records) into a String
367     *
368     * Field may be padded with trailing 0xff's. The decode stops
369     * at the first 0xff encountered.
370     *
371     * Additionally, in some country(ex. Korea), there are non-ASCII or MBCS characters.
372     * If a character set is given, characters in data are treat as MBCS.
373     */
374    public static String
375    gsm8BitUnpackedToString(byte[] data, int offset, int length, String characterset) {
376        boolean isMbcs = false;
377        Charset charset = null;
378        ByteBuffer mbcsBuffer = null;
379
380        if (!TextUtils.isEmpty(characterset)
381                && !characterset.equalsIgnoreCase("us-ascii")
382                && Charset.isSupported(characterset)) {
383            isMbcs = true;
384            charset = Charset.forName(characterset);
385            mbcsBuffer = ByteBuffer.allocate(2);
386        }
387        boolean prevWasEscape;
388        StringBuilder ret = new StringBuilder(length);
389
390        prevWasEscape = false;
391        for (int i = offset ; i < offset + length ; i++) {
392            // Never underestimate the pain that can be caused
393            // by signed bytes
394            int c = data[i] & 0xff;
395
396            if (c == 0xff) {
397                break;
398            } else if (c == GSM_EXTENDED_ESCAPE) {
399                if (prevWasEscape) {
400                    // Two escape chars in a row
401                    // We treat this as a space
402                    // See Note 1 in table 6.2.1.1 of TS 23.038 v7.00
403                    ret.append(' ');
404                    prevWasEscape = false;
405                } else {
406                    prevWasEscape = true;
407                }
408            } else {
409                if (prevWasEscape) {
410                    ret.append((char)gsmExtendedToChar.get(c, ' '));
411                } else {
412                    if (!isMbcs || c < 0x80 || i + 1 >= offset + length) {
413                        ret.append((char)gsmToChar.get(c, ' '));
414                    } else {
415                        // isMbcs must be true. So both mbcsBuffer and charset are initialized.
416                        mbcsBuffer.clear();
417                        mbcsBuffer.put(data, i++, 2);
418                        mbcsBuffer.flip();
419                        ret.append(charset.decode(mbcsBuffer).toString());
420                    }
421                }
422                prevWasEscape = false;
423            }
424        }
425
426        return ret.toString();
427    }
428
429    /**
430     * Convert a string into an 8-bit unpacked GSM alphabet byte
431     * array
432     */
433    public static byte[]
434    stringToGsm8BitPacked(String s) {
435        byte[] ret;
436
437        int septets = 0;
438
439        septets = countGsmSeptets(s);
440
441        // Enough for all the septets and the length byte prefix
442        ret = new byte[septets];
443
444        stringToGsm8BitUnpackedField(s, ret, 0, ret.length);
445
446        return ret;
447    }
448
449
450    /**
451     * Write a String into a GSM 8-bit unpacked field of
452     * @param length size at @param offset in @param dest
453     *
454     * Field is padded with 0xff's, string is truncated if necessary
455     */
456
457    public static void
458    stringToGsm8BitUnpackedField(String s, byte dest[], int offset, int length) {
459        int outByteIndex = offset;
460
461        // Septets are stored in byte-aligned octets
462        for (int i = 0, sz = s.length()
463                ; i < sz && (outByteIndex - offset) < length
464                ; i++
465        ) {
466            char c = s.charAt(i);
467
468            int v = GsmAlphabet.charToGsm(c);
469
470            if (v == GSM_EXTENDED_ESCAPE) {
471                // make sure we can fit an escaped char
472                if (! (outByteIndex + 1 - offset < length)) {
473                    break;
474                }
475
476                dest[outByteIndex++] = GSM_EXTENDED_ESCAPE;
477
478                v = GsmAlphabet.charToGsmExtended(c);
479            }
480
481            dest[outByteIndex++] = (byte)v;
482        }
483
484        // pad with 0xff's
485        while((outByteIndex - offset) < length) {
486            dest[outByteIndex++] = (byte)0xff;
487        }
488    }
489
490    /**
491     * Returns the count of 7-bit GSM alphabet characters
492     * needed to represent this character. Counts unencodable char as 1 septet.
493     */
494    public static int
495    countGsmSeptets(char c) {
496        try {
497            return countGsmSeptets(c, false);
498        } catch (EncodeException ex) {
499            // This should never happen.
500            return 0;
501        }
502    }
503
504    /**
505     * Returns the count of 7-bit GSM alphabet characters
506     * needed to represent this character
507     * @param throwsException If true, throws EncodeException if unencodable
508     * char. Otherwise, counts invalid char as 1 septet
509     */
510    public static int
511    countGsmSeptets(char c, boolean throwsException) throws EncodeException {
512        if (charToGsm.get(c, -1) != -1) {
513            return 1;
514        }
515
516        if (charToGsmExtended.get(c, -1) != -1) {
517            return 2;
518        }
519
520        if (throwsException) {
521            throw new EncodeException(c);
522        } else {
523            // count as a space char
524            return 1;
525        }
526    }
527
528    /**
529     * Returns the count of 7-bit GSM alphabet characters
530     * needed to represent this string. Counts unencodable char as 1 septet.
531     */
532    public static int
533    countGsmSeptets(CharSequence s) {
534        try {
535            return countGsmSeptets(s, false);
536        } catch (EncodeException ex) {
537            // this should never happen
538            return 0;
539        }
540    }
541
542    /**
543     * Returns the count of 7-bit GSM alphabet characters
544     * needed to represent this string.
545     * @param throwsException If true, throws EncodeException if unencodable
546     * char. Otherwise, counts invalid char as 1 septet
547     */
548    public static int
549    countGsmSeptets(CharSequence s, boolean throwsException) throws EncodeException {
550        int charIndex = 0;
551        int sz = s.length();
552        int count = 0;
553
554        while (charIndex < sz) {
555            count += countGsmSeptets(s.charAt(charIndex), throwsException);
556            charIndex++;
557        }
558
559        return count;
560    }
561
562    /**
563     * Returns the index into <code>s</code> of the first character
564     * after <code>limit</code> septets have been reached, starting at
565     * index <code>start</code>.  This is used when dividing messages
566     * into units within the SMS message size limit.
567     *
568     * @param s source string
569     * @param start index of where to start counting septets
570     * @param limit maximum septets to include,
571     *   e.g. <code>MAX_USER_DATA_SEPTETS</code>
572     * @return index of first character that won't fit, or the length
573     *   of the entire string if everything fits
574     */
575    public static int
576    findGsmSeptetLimitIndex(String s, int start, int limit) {
577        int accumulator = 0;
578        int size = s.length();
579
580        for (int i = start; i < size; i++) {
581            accumulator += countGsmSeptets(s.charAt(i));
582            if (accumulator > limit) {
583                return i;
584            }
585        }
586        return size;
587    }
588
589    // Set in the static initializer
590    private static int sGsmSpaceChar;
591
592    private static final SparseIntArray charToGsm = new SparseIntArray();
593    private static final SparseIntArray gsmToChar = new SparseIntArray();
594    private static final SparseIntArray charToGsmExtended = new SparseIntArray();
595    private static final SparseIntArray gsmExtendedToChar = new SparseIntArray();
596
597    static {
598        int i = 0;
599
600        charToGsm.put('@', i++);
601        charToGsm.put('\u00a3', i++);
602        charToGsm.put('$', i++);
603        charToGsm.put('\u00a5', i++);
604        charToGsm.put('\u00e8', i++);
605        charToGsm.put('\u00e9', i++);
606        charToGsm.put('\u00f9', i++);
607        charToGsm.put('\u00ec', i++);
608        charToGsm.put('\u00f2', i++);
609        charToGsm.put('\u00c7', i++);
610        charToGsm.put('\n', i++);
611        charToGsm.put('\u00d8', i++);
612        charToGsm.put('\u00f8', i++);
613        charToGsm.put('\r', i++);
614        charToGsm.put('\u00c5', i++);
615        charToGsm.put('\u00e5', i++);
616
617        charToGsm.put('\u0394', i++);
618        charToGsm.put('_', i++);
619        charToGsm.put('\u03a6', i++);
620        charToGsm.put('\u0393', i++);
621        charToGsm.put('\u039b', i++);
622        charToGsm.put('\u03a9', i++);
623        charToGsm.put('\u03a0', i++);
624        charToGsm.put('\u03a8', i++);
625        charToGsm.put('\u03a3', i++);
626        charToGsm.put('\u0398', i++);
627        charToGsm.put('\u039e', i++);
628        charToGsm.put('\uffff', i++);
629        charToGsm.put('\u00c6', i++);
630        charToGsm.put('\u00e6', i++);
631        charToGsm.put('\u00df', i++);
632        charToGsm.put('\u00c9', i++);
633
634        charToGsm.put(' ', i++);
635        charToGsm.put('!', i++);
636        charToGsm.put('"', i++);
637        charToGsm.put('#', i++);
638        charToGsm.put('\u00a4', i++);
639        charToGsm.put('%', i++);
640        charToGsm.put('&', i++);
641        charToGsm.put('\'', i++);
642        charToGsm.put('(', i++);
643        charToGsm.put(')', i++);
644        charToGsm.put('*', i++);
645        charToGsm.put('+', i++);
646        charToGsm.put(',', i++);
647        charToGsm.put('-', i++);
648        charToGsm.put('.', i++);
649        charToGsm.put('/', i++);
650
651        charToGsm.put('0', i++);
652        charToGsm.put('1', i++);
653        charToGsm.put('2', i++);
654        charToGsm.put('3', i++);
655        charToGsm.put('4', i++);
656        charToGsm.put('5', i++);
657        charToGsm.put('6', i++);
658        charToGsm.put('7', i++);
659        charToGsm.put('8', i++);
660        charToGsm.put('9', i++);
661        charToGsm.put(':', i++);
662        charToGsm.put(';', i++);
663        charToGsm.put('<', i++);
664        charToGsm.put('=', i++);
665        charToGsm.put('>', i++);
666        charToGsm.put('?', i++);
667
668        charToGsm.put('\u00a1', i++);
669        charToGsm.put('A', i++);
670        charToGsm.put('B', i++);
671        charToGsm.put('C', i++);
672        charToGsm.put('D', i++);
673        charToGsm.put('E', i++);
674        charToGsm.put('F', i++);
675        charToGsm.put('G', i++);
676        charToGsm.put('H', i++);
677        charToGsm.put('I', i++);
678        charToGsm.put('J', i++);
679        charToGsm.put('K', i++);
680        charToGsm.put('L', i++);
681        charToGsm.put('M', i++);
682        charToGsm.put('N', i++);
683        charToGsm.put('O', i++);
684
685        charToGsm.put('P', i++);
686        charToGsm.put('Q', i++);
687        charToGsm.put('R', i++);
688        charToGsm.put('S', i++);
689        charToGsm.put('T', i++);
690        charToGsm.put('U', i++);
691        charToGsm.put('V', i++);
692        charToGsm.put('W', i++);
693        charToGsm.put('X', i++);
694        charToGsm.put('Y', i++);
695        charToGsm.put('Z', i++);
696        charToGsm.put('\u00c4', i++);
697        charToGsm.put('\u00d6', i++);
698        charToGsm.put('\u00d1', i++);
699        charToGsm.put('\u00dc', i++);
700        charToGsm.put('\u00a7', i++);
701
702        charToGsm.put('\u00bf', i++);
703        charToGsm.put('a', i++);
704        charToGsm.put('b', i++);
705        charToGsm.put('c', i++);
706        charToGsm.put('d', i++);
707        charToGsm.put('e', i++);
708        charToGsm.put('f', i++);
709        charToGsm.put('g', i++);
710        charToGsm.put('h', i++);
711        charToGsm.put('i', i++);
712        charToGsm.put('j', i++);
713        charToGsm.put('k', i++);
714        charToGsm.put('l', i++);
715        charToGsm.put('m', i++);
716        charToGsm.put('n', i++);
717        charToGsm.put('o', i++);
718
719        charToGsm.put('p', i++);
720        charToGsm.put('q', i++);
721        charToGsm.put('r', i++);
722        charToGsm.put('s', i++);
723        charToGsm.put('t', i++);
724        charToGsm.put('u', i++);
725        charToGsm.put('v', i++);
726        charToGsm.put('w', i++);
727        charToGsm.put('x', i++);
728        charToGsm.put('y', i++);
729        charToGsm.put('z', i++);
730        charToGsm.put('\u00e4', i++);
731        charToGsm.put('\u00f6', i++);
732        charToGsm.put('\u00f1', i++);
733        charToGsm.put('\u00fc', i++);
734        charToGsm.put('\u00e0', i++);
735
736
737        charToGsmExtended.put('\f', 10);
738        charToGsmExtended.put('^', 20);
739        charToGsmExtended.put('{', 40);
740        charToGsmExtended.put('}', 41);
741        charToGsmExtended.put('\\', 47);
742        charToGsmExtended.put('[', 60);
743        charToGsmExtended.put('~', 61);
744        charToGsmExtended.put(']', 62);
745        charToGsmExtended.put('|', 64);
746        charToGsmExtended.put('\u20ac', 101);
747
748        int size = charToGsm.size();
749        for (int j=0; j<size; j++) {
750            gsmToChar.put(charToGsm.valueAt(j), charToGsm.keyAt(j));
751        }
752
753        size = charToGsmExtended.size();
754        for (int j=0; j<size; j++) {
755            gsmExtendedToChar.put(charToGsmExtended.valueAt(j), charToGsmExtended.keyAt(j));
756        }
757
758
759        sGsmSpaceChar = charToGsm.get(' ');
760    }
761
762
763}
764