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