FileDocCategorySizeDatePackage
GsmAlphabet.javaAPI DocAndroid 5.1 API72469Thu Mar 12 22:22:42 GMT 2015com.android.internal.telephony

GsmAlphabet

public class GsmAlphabet extends Object
This class implements the character set mapping between the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1 and UTF-16 {@hide}

Fields Summary
private static final String
TAG
public static final byte
GSM_EXTENDED_ESCAPE
This escapes extended characters, and when present indicates that the following character should be looked up in the "extended" table. gsmToChar(GSM_EXTENDED_ESCAPE) returns 0xffff
public static final int
UDH_SEPTET_COST_LENGTH
User data header requires one octet for length. Count as one septet, because all combinations of header elements below will have at least one free bit when padding to the nearest septet boundary.
public static final int
UDH_SEPTET_COST_ONE_SHIFT_TABLE
Using a non-default language locking shift table OR single shift table requires a user data header of 3 octets, or 4 septets, plus UDH length.
public static final int
UDH_SEPTET_COST_TWO_SHIFT_TABLES
Using a non-default language locking shift table AND single shift table requires a user data header of 6 octets, or 7 septets, plus UDH length.
public static final int
UDH_SEPTET_COST_CONCATENATED_MESSAGE
Multi-part messages require a user data header of 5 octets, or 6 septets, plus UDH length.
private static final android.util.SparseIntArray[]
sCharsToGsmTables
Reverse mapping from Unicode characters to indexes into language tables.
private static final android.util.SparseIntArray[]
sCharsToShiftTables
Reverse mapping from Unicode characters to indexes into language shift tables.
private static int[]
sEnabledSingleShiftTables
OEM configured list of enabled national language single shift tables for encoding.
private static int[]
sEnabledLockingShiftTables
OEM configured list of enabled national language locking shift tables for encoding.
private static int
sHighestEnabledSingleShiftCode
Highest language code to include in array of single shift counters.
private static boolean
sDisableCountryEncodingCheck
Flag to bypass check for country-specific overlays (for test cases only).
private static final String[]
sLanguageTables
GSM default 7 bit alphabet plus national language locking shift character tables. Comment lines above strings indicate the lower four bits of the table position.
private static final String[]
sLanguageShiftTables
GSM default extension table plus national language single shift character tables.
Constructors Summary
private GsmAlphabet()


       
Methods Summary
public static intcharToGsm(char c)
Converts a char to a GSM 7 bit table index. Returns ' ' in GSM alphabet if there's no possible match. Returns GSM_EXTENDED_ESCAPE if this character is in the extended table. In this case, you must call charToGsmExtended() for the value that should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string.

param
c the character to convert
return
the GSM 7 bit table index for the specified character

        try {
            return charToGsm(c, false);
        } catch (EncodeException ex) {
            // this should never happen
            return sCharsToGsmTables[0].get(' ", ' ");
        }
    
public static intcharToGsm(char c, boolean throwException)
Converts a char to a GSM 7 bit table index. Returns GSM_EXTENDED_ESCAPE if this character is in the extended table. In this case, you must call charToGsmExtended() for the value that should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string.

param
c the character to convert
param
throwException If true, throws EncodeException on invalid char. If false, returns GSM alphabet ' ' char.
throws
EncodeException encode error when throwException is true
return
the GSM 7 bit table index for the specified character

        int ret;

        ret = sCharsToGsmTables[0].get(c, -1);

        if (ret == -1) {
            ret = sCharsToShiftTables[0].get(c, -1);

            if (ret == -1) {
                if (throwException) {
                    throw new EncodeException(c);
                } else {
                    return sCharsToGsmTables[0].get(' ", ' ");
                }
            } else {
                return GSM_EXTENDED_ESCAPE;
            }
        }

        return ret;
    
public static intcharToGsmExtended(char c)
Converts a char to an extended GSM 7 bit table index. Extended chars should be escaped with GSM_EXTENDED_ESCAPE. Returns ' ' in GSM alphabet if there's no possible match.

param
c the character to convert
return
the GSM 7 bit extended table index for the specified character

        int ret;

        ret = sCharsToShiftTables[0].get(c, -1);

        if (ret == -1) {
            return sCharsToGsmTables[0].get(' ", ' ");
        }

        return ret;
    
public static intcountGsmSeptets(char c)
Returns the count of 7-bit GSM alphabet characters needed to represent this character. Counts unencodable char as 1 septet.

param
c the character to examine
return
the number of septets for this character

        try {
            return countGsmSeptets(c, false);
        } catch (EncodeException ex) {
            // This should never happen.
            return 0;
        }
    
public static intcountGsmSeptets(char c, boolean throwsException)
Returns the count of 7-bit GSM alphabet characters needed to represent this character using the default 7 bit GSM alphabet.

param
c the character to examine
param
throwsException If true, throws EncodeException if unencodable char. Otherwise, counts invalid char as 1 septet.
return
the number of septets for this character
throws
EncodeException the character can't be encoded and throwsException is true

        if (sCharsToGsmTables[0].get(c, -1) != -1) {
            return 1;
        }

        if (sCharsToShiftTables[0].get(c, -1) != -1) {
            return 2;
        }

        if (throwsException) {
            throw new EncodeException(c);
        } else {
            // count as a space char
            return 1;
        }
    
public static com.android.internal.telephony.GsmAlphabet$TextEncodingDetailscountGsmSeptets(java.lang.CharSequence s, boolean use7bitOnly)
Returns the count of 7-bit GSM alphabet characters needed to represent this string, and the language table and language shift table used to achieve this result. For multi-part text messages, each message part may use its own language table encoding as specified in the message header for that message. However, this method will only return the optimal encoding for the message as a whole. When the individual pieces are encoded, a more optimal encoding may be chosen for each piece of the message, but the message will be split into pieces based on the encoding chosen for the message as a whole.

param
s the Unicode string that will be encoded
param
use7bitOnly allow using space in place of unencodable character if true, using the language table pair with the fewest unencodable characters
return
a TextEncodingDetails object containing the message and character counts for the most efficient 7-bit encoding, or null if there are no suitable language tables to encode the string.

        // Load enabled language tables from config.xml, including any MCC overlays
        if (!sDisableCountryEncodingCheck) {
            enableCountrySpecificEncodings();
        }
        // fast path for common case where no national language shift tables are enabled
        if (sEnabledSingleShiftTables.length + sEnabledLockingShiftTables.length == 0) {
            TextEncodingDetails ted = new TextEncodingDetails();
            int septets = GsmAlphabet.countGsmSeptetsUsingTables(s, use7bitOnly, 0, 0);
            if (septets == -1) {
                return null;
            }
            ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
            ted.codeUnitCount = septets;
            if (septets > SmsConstants.MAX_USER_DATA_SEPTETS) {
                ted.msgCount = (septets + (SmsConstants.MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) /
                        SmsConstants.MAX_USER_DATA_SEPTETS_WITH_HEADER;
                ted.codeUnitsRemaining = (ted.msgCount *
                        SmsConstants.MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets;
            } else {
                ted.msgCount = 1;
                ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
            }
            ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
            return ted;
        }

        int maxSingleShiftCode = sHighestEnabledSingleShiftCode;
        List<LanguagePairCount> lpcList = new ArrayList<LanguagePairCount>(
                sEnabledLockingShiftTables.length + 1);

        // Always add default GSM 7-bit alphabet table
        lpcList.add(new LanguagePairCount(0));
        for (int i : sEnabledLockingShiftTables) {
            // Avoid adding default table twice in case 0 is in the list of allowed tables
            if (i != 0 && !sLanguageTables[i].isEmpty()) {
                lpcList.add(new LanguagePairCount(i));
            }
        }

        int sz = s.length();
        // calculate septet count for each valid table / shift table pair
        for (int i = 0; i < sz && !lpcList.isEmpty(); i++) {
            char c = s.charAt(i);
            if (c == GSM_EXTENDED_ESCAPE) {
                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
                continue;
            }
            // iterate through enabled locking shift tables
            for (LanguagePairCount lpc : lpcList) {
                int tableIndex = sCharsToGsmTables[lpc.languageCode].get(c, -1);
                if (tableIndex == -1) {
                    // iterate through single shift tables for this locking table
                    for (int table = 0; table <= maxSingleShiftCode; table++) {
                        if (lpc.septetCounts[table] != -1) {
                            int shiftTableIndex = sCharsToShiftTables[table].get(c, -1);
                            if (shiftTableIndex == -1) {
                                if (use7bitOnly) {
                                    // can't encode char, use space instead
                                    lpc.septetCounts[table]++;
                                    lpc.unencodableCounts[table]++;
                                } else {
                                    // can't encode char, remove language pair from list
                                    lpc.septetCounts[table] = -1;
                                }
                            } else {
                                // encode as Escape + index into shift table
                                lpc.septetCounts[table] += 2;
                            }
                        }
                    }
                } else {
                    // encode as index into locking shift table for all pairs
                    for (int table = 0; table <= maxSingleShiftCode; table++) {
                        if (lpc.septetCounts[table] != -1) {
                            lpc.septetCounts[table]++;
                        }
                    }
                }
            }
        }

        // find the least cost encoding (lowest message count and most code units remaining)
        TextEncodingDetails ted = new TextEncodingDetails();
        ted.msgCount = Integer.MAX_VALUE;
        ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
        int minUnencodableCount = Integer.MAX_VALUE;
        for (LanguagePairCount lpc : lpcList) {
            for (int shiftTable = 0; shiftTable <= maxSingleShiftCode; shiftTable++) {
                int septets = lpc.septetCounts[shiftTable];
                if (septets == -1) {
                    continue;
                }
                int udhLength;
                if (lpc.languageCode != 0 && shiftTable != 0) {
                    udhLength = UDH_SEPTET_COST_LENGTH + UDH_SEPTET_COST_TWO_SHIFT_TABLES;
                } else if (lpc.languageCode != 0 || shiftTable != 0) {
                    udhLength = UDH_SEPTET_COST_LENGTH + UDH_SEPTET_COST_ONE_SHIFT_TABLE;
                } else {
                    udhLength = 0;
                }
                int msgCount;
                int septetsRemaining;
                if (septets + udhLength > SmsConstants.MAX_USER_DATA_SEPTETS) {
                    if (udhLength == 0) {
                        udhLength = UDH_SEPTET_COST_LENGTH;
                    }
                    udhLength += UDH_SEPTET_COST_CONCATENATED_MESSAGE;
                    int septetsPerMessage = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
                    msgCount = (septets + septetsPerMessage - 1) / septetsPerMessage;
                    septetsRemaining = (msgCount * septetsPerMessage) - septets;
                } else {
                    msgCount = 1;
                    septetsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength - septets;
                }
                // for 7-bit only mode, use language pair with the least unencodable chars
                int unencodableCount = lpc.unencodableCounts[shiftTable];
                if (use7bitOnly && unencodableCount > minUnencodableCount) {
                    continue;
                }
                if ((use7bitOnly && unencodableCount < minUnencodableCount)
                        || msgCount < ted.msgCount || (msgCount == ted.msgCount
                        && septetsRemaining > ted.codeUnitsRemaining)) {
                    minUnencodableCount = unencodableCount;
                    ted.msgCount = msgCount;
                    ted.codeUnitCount = septets;
                    ted.codeUnitsRemaining = septetsRemaining;
                    ted.languageTable = lpc.languageCode;
                    ted.languageShiftTable = shiftTable;
                }
            }
        }

        if (ted.msgCount == Integer.MAX_VALUE) {
            return null;
        }

        return ted;
    
public static intcountGsmSeptetsUsingTables(java.lang.CharSequence s, boolean use7bitOnly, int languageTable, int languageShiftTable)
Returns the count of 7-bit GSM alphabet characters needed to represent this string, using the specified 7-bit language table and extension table (0 for GSM default tables).

param
s the Unicode string that will be encoded
param
use7bitOnly allow using space in place of unencodable character if true, otherwise, return -1 if any characters are unencodable
param
languageTable the 7 bit language table, or 0 for the default GSM alphabet
param
languageShiftTable the 7 bit single shift language table, or 0 for the default GSM extension table
return
the septet count for s using the specified language tables, or -1 if any characters are unencodable and use7bitOnly is false

        int count = 0;
        int sz = s.length();
        SparseIntArray charToLanguageTable = sCharsToGsmTables[languageTable];
        SparseIntArray charToShiftTable = sCharsToShiftTables[languageShiftTable];
        for (int i = 0; i < sz; i++) {
            char c = s.charAt(i);
            if (c == GSM_EXTENDED_ESCAPE) {
                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
                continue;
            }
            if (charToLanguageTable.get(c, -1) != -1) {
                count++;
            } else if (charToShiftTable.get(c, -1) != -1) {
                count += 2; // escape + shift table index
            } else if (use7bitOnly) {
                count++;    // encode as space
            } else {
                return -1;  // caller must check for this case
            }
        }
        return count;
    
private static voidenableCountrySpecificEncodings()
Enable country-specific language tables from MCC-specific overlays.

context
the context to use to get the TelephonyManager

        Resources r = Resources.getSystem();
        // See comments in frameworks/base/core/res/res/values/config.xml for allowed values
        sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables);
        sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables);

        if (sEnabledSingleShiftTables.length > 0) {
            sHighestEnabledSingleShiftCode =
                    sEnabledSingleShiftTables[sEnabledSingleShiftTables.length-1];
        } else {
            sHighestEnabledSingleShiftCode = 0;
        }
    
public static intfindGsmSeptetLimitIndex(java.lang.String s, int start, int limit, int langTable, int langShiftTable)
Returns the index into s of the first character after limit septets have been reached, starting at index start. This is used when dividing messages into units within the SMS message size limit.

param
s source string
param
start index of where to start counting septets
param
limit maximum septets to include, e.g. MAX_USER_DATA_SEPTETS
param
langTable the 7 bit character table to use (0 for default GSM 7-bit alphabet)
param
langShiftTable the 7 bit shift table to use (0 for default GSM extension table)
return
index of first character that won't fit, or the length of the entire string if everything fits

        int accumulator = 0;
        int size = s.length();

        SparseIntArray charToLangTable = sCharsToGsmTables[langTable];
        SparseIntArray charToLangShiftTable = sCharsToShiftTables[langShiftTable];
        for (int i = start; i < size; i++) {
            int encodedSeptet = charToLangTable.get(s.charAt(i), -1);
            if (encodedSeptet == -1) {
                encodedSeptet = charToLangShiftTable.get(s.charAt(i), -1);
                if (encodedSeptet == -1) {
                    // char not found, assume we're replacing with space
                    accumulator++;
                } else {
                    accumulator += 2;  // escape character + shift table index
                }
            } else {
                accumulator++;
            }
            if (accumulator > limit) {
                return i;
            }
        }
        return size;
    
static synchronized int[]getEnabledLockingShiftTables()
Return the array of enabled national language locking shift tables for SMS encoding. This is used for unit testing. The returned array is not a copy, so the caller should be careful not to modify it.

return
the list of enabled locking shift tables

        return sEnabledLockingShiftTables;
    
static synchronized int[]getEnabledSingleShiftTables()
Return the array of enabled national language single shift tables for SMS encoding. This is used for unit testing. The returned array is not a copy, so the caller should be careful not to modify it.

return
the list of enabled single shift tables

        return sEnabledSingleShiftTables;
    
public static java.lang.Stringgsm7BitPackedToString(byte[] pdu, int offset, int lengthSeptets)
Convert a GSM alphabet 7 bit packed string (SMS string) into a {@link java.lang.String}. See TS 23.038 6.1.2.1 for SMS Character Packing

param
pdu the raw data from the pdu
param
offset the byte offset of
param
lengthSeptets string length in septets, not bytes
return
String representation or null on decoding exception

        return gsm7BitPackedToString(pdu, offset, lengthSeptets, 0, 0, 0);
    
public static java.lang.Stringgsm7BitPackedToString(byte[] pdu, int offset, int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable)
Convert a GSM alphabet 7 bit packed string (SMS string) into a {@link java.lang.String}. See TS 23.038 6.1.2.1 for SMS Character Packing

param
pdu the raw data from the pdu
param
offset the byte offset of
param
lengthSeptets string length in septets, not bytes
param
numPaddingBits the number of padding bits before the start of the string in the first byte
param
languageTable the 7 bit language table, or 0 for the default GSM alphabet
param
shiftTable the 7 bit single shift language table, or 0 for the default GSM extension table
return
String representation or null on decoding exception

        StringBuilder ret = new StringBuilder(lengthSeptets);

        if (languageTable < 0 || languageTable > sLanguageTables.length) {
            Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
            languageTable = 0;
        }
        if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
            Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
            shiftTable = 0;
        }

        try {
            boolean prevCharWasEscape = false;
            String languageTableToChar = sLanguageTables[languageTable];
            String shiftTableToChar = sLanguageShiftTables[shiftTable];

            if (languageTableToChar.isEmpty()) {
                Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
                languageTableToChar = sLanguageTables[0];
            }
            if (shiftTableToChar.isEmpty()) {
                Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
                shiftTableToChar = sLanguageShiftTables[0];
            }

            for (int i = 0 ; i < lengthSeptets ; i++) {
                int bitOffset = (7 * i) + numPaddingBits;

                int byteOffset = bitOffset / 8;
                int shift = bitOffset % 8;
                int gsmVal;

                gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));

                // if it crosses a byte boundary
                if (shift > 1) {
                    // set msb bits to 0
                    gsmVal &= 0x7f >> (shift - 1);

                    gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
                }

                if (prevCharWasEscape) {
                    if (gsmVal == GSM_EXTENDED_ESCAPE) {
                        ret.append(' ");    // display ' ' for reserved double escape sequence
                    } else {
                        char c = shiftTableToChar.charAt(gsmVal);
                        if (c == ' ") {
                            ret.append(languageTableToChar.charAt(gsmVal));
                        } else {
                            ret.append(c);
                        }
                    }
                    prevCharWasEscape = false;
                } else if (gsmVal == GSM_EXTENDED_ESCAPE) {
                    prevCharWasEscape = true;
                } else {
                    ret.append(languageTableToChar.charAt(gsmVal));
                }
            }
        } catch (RuntimeException ex) {
            Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
            return null;
        }

        return ret.toString();
    
public static java.lang.Stringgsm8BitUnpackedToString(byte[] data, int offset, int length)
Convert a GSM alphabet string that's stored in 8-bit unpacked format (as it often appears in SIM records) into a String Field may be padded with trailing 0xff's. The decode stops at the first 0xff encountered.

param
data the byte array to decode
param
offset array offset for the first character to decode
param
length the number of bytes to decode
return
the decoded string

        return gsm8BitUnpackedToString(data, offset, length, "");
    
public static java.lang.Stringgsm8BitUnpackedToString(byte[] data, int offset, int length, java.lang.String characterset)
Convert a GSM alphabet string that's stored in 8-bit unpacked format (as it often appears in SIM records) into a String Field may be padded with trailing 0xff's. The decode stops at the first 0xff encountered. Additionally, in some country(ex. Korea), there are non-ASCII or MBCS characters. If a character set is given, characters in data are treat as MBCS.

        boolean isMbcs = false;
        Charset charset = null;
        ByteBuffer mbcsBuffer = null;

        if (!TextUtils.isEmpty(characterset)
                && !characterset.equalsIgnoreCase("us-ascii")
                && Charset.isSupported(characterset)) {
            isMbcs = true;
            charset = Charset.forName(characterset);
            mbcsBuffer = ByteBuffer.allocate(2);
        }

        // Always use GSM 7 bit default alphabet table for this method
        String languageTableToChar = sLanguageTables[0];
        String shiftTableToChar = sLanguageShiftTables[0];

        StringBuilder ret = new StringBuilder(length);
        boolean prevWasEscape = false;
        for (int i = offset ; i < offset + length ; i++) {
            // Never underestimate the pain that can be caused
            // by signed bytes
            int c = data[i] & 0xff;

            if (c == 0xff) {
                break;
            } else if (c == GSM_EXTENDED_ESCAPE) {
                if (prevWasEscape) {
                    // Two escape chars in a row
                    // We treat this as a space
                    // See Note 1 in table 6.2.1.1 of TS 23.038 v7.00
                    ret.append(' ");
                    prevWasEscape = false;
                } else {
                    prevWasEscape = true;
                }
            } else {
                if (prevWasEscape) {
                    char shiftChar =
                            c < shiftTableToChar.length() ? shiftTableToChar.charAt(c) : ' ";
                    if (shiftChar == ' ") {
                        // display character from main table if not present in shift table
                        if (c < languageTableToChar.length()) {
                            ret.append(languageTableToChar.charAt(c));
                        } else {
                            ret.append(' ");
                        }
                    } else {
                        ret.append(shiftChar);
                    }
                } else {
                    if (!isMbcs || c < 0x80 || i + 1 >= offset + length) {
                        if (c < languageTableToChar.length()) {
                            ret.append(languageTableToChar.charAt(c));
                        } else {
                            ret.append(' ");
                        }
                    } else {
                        // isMbcs must be true. So both mbcsBuffer and charset are initialized.
                        mbcsBuffer.clear();
                        mbcsBuffer.put(data, i++, 2);
                        mbcsBuffer.flip();
                        ret.append(charset.decode(mbcsBuffer).toString());
                    }
                }
                prevWasEscape = false;
            }
        }

        return ret.toString();
    
public static chargsmExtendedToChar(int gsmChar)
Converts a character in the extended GSM alphabet into a char if GSM_EXTENDED_ESCAPE is passed, ' ' is returned since no second extension page has yet been defined (see Note 1 in table 6.2.1.1 of TS 23.038 v7.00) If an unmappable value is passed, the character from the GSM 7 bit default table will be used (table 6.2.1.1 of TS 23.038).

param
gsmChar the GSM 7 bit extended table index to convert
return
the decoded character

        if (gsmChar == GSM_EXTENDED_ESCAPE) {
            return ' ";
        } else if (gsmChar >= 0 && gsmChar < 128) {
            char c = sLanguageShiftTables[0].charAt(gsmChar);
            if (c == ' ") {
                return sLanguageTables[0].charAt(gsmChar);
            } else {
                return c;
            }
        } else {
            return ' ";     // out of range
        }
    
public static chargsmToChar(int gsmChar)
Converts a character in the GSM alphabet into a char. If GSM_EXTENDED_ESCAPE is passed, 0xffff is returned. In this case, the following character in the stream should be decoded with gsmExtendedToChar(). If an unmappable value is passed (one greater than 127), ' ' is returned.

param
gsmChar the GSM 7 bit table index to convert
return
the decoded character

        if (gsmChar >= 0 && gsmChar < 128) {
            return sLanguageTables[0].charAt(gsmChar);
        } else {
            return ' ";
        }
    
public static booleanisGsmSeptets(char c)

        if (sCharsToGsmTables[0].get(c, -1) != -1) {
            return true;
        }

        if (sCharsToShiftTables[0].get(c, -1) != -1) {
            return true;
        }

        return false;
    
private static voidpackSmsChar(byte[] packedChars, int bitOffset, int value)
Pack a 7-bit char into its appropriate place in a byte array

param
packedChars the destination byte array
param
bitOffset the bit offset that the septet should be packed at (septet index * 7)
param
value the 7-bit character to store

        int byteOffset = bitOffset / 8;
        int shift = bitOffset % 8;

        packedChars[++byteOffset] |= value << shift;

        if (shift > 1) {
            packedChars[++byteOffset] = (byte)(value >> (8 - shift));
        }
    
static synchronized voidsetEnabledLockingShiftTables(int[] tables)
Modify the array of enabled national language locking shift tables for SMS encoding. This is used for unit testing, but could also be used to modify the enabled encodings based on the active MCC/MNC, for example.

param
tables the new list of enabled locking shift tables

        sEnabledLockingShiftTables = tables;
        sDisableCountryEncodingCheck = true;
    
static synchronized voidsetEnabledSingleShiftTables(int[] tables)
Modify the array of enabled national language single shift tables for SMS encoding. This is used for unit testing, but could also be used to modify the enabled encodings based on the active MCC/MNC, for example.

param
tables the new list of enabled single shift tables

        sEnabledSingleShiftTables = tables;
        sDisableCountryEncodingCheck = true;

        if (tables.length > 0) {
            sHighestEnabledSingleShiftCode = tables[tables.length - 1];
        } else {
            sHighestEnabledSingleShiftCode = 0;
        }
    
public static byte[]stringToGsm7BitPacked(java.lang.String data, int languageTable, int languageShiftTable)
Converts a String into a byte array containing the 7-bit packed GSM Alphabet representation of the string. Unencodable chars are encoded as spaces Byte 0 in the returned byte array is the count of septets used The returned byte array is the minimum size required to store the packed septets. The returned array cannot contain more than 255 septets.

param
data the data string to encode
param
languageTable the 7 bit language table, or 0 for the default GSM alphabet
param
languageShiftTable the 7 bit single shift language table, or 0 for the default GSM extension table
return
the encoded string
throws
EncodeException if String is too large to encode

        return stringToGsm7BitPacked(data, 0, true, languageTable, languageShiftTable);
    
public static byte[]stringToGsm7BitPacked(java.lang.String data, int startingSeptetOffset, boolean throwException, int languageTable, int languageShiftTable)
Converts a String into a byte array containing the 7-bit packed GSM Alphabet representation of the string. Byte 0 in the returned byte array is the count of septets used The returned byte array is the minimum size required to store the packed septets. The returned array cannot contain more than 255 septets.

param
data the text to convert to septets
param
startingSeptetOffset the number of padding septets to put before the character data at the beginning of the array
param
throwException If true, throws EncodeException on invalid char. If false, replaces unencodable char with GSM alphabet space char.
param
languageTable the 7 bit language table, or 0 for the default GSM alphabet
param
languageShiftTable the 7 bit single shift language table, or 0 for the default GSM extension table
return
the encoded message
throws
EncodeException if String is too large to encode

        int dataLen = data.length();
        int septetCount = countGsmSeptetsUsingTables(data, !throwException,
                languageTable, languageShiftTable);
        if (septetCount == -1) {
            throw new EncodeException("countGsmSeptetsUsingTables(): unencodable char");
        }
        septetCount += startingSeptetOffset;
        if (septetCount > 255) {
            throw new EncodeException("Payload cannot exceed 255 septets");
        }
        int byteCount = ((septetCount * 7) + 7) / 8;
        byte[] ret = new byte[byteCount + 1];  // Include space for one byte length prefix.
        SparseIntArray charToLanguageTable = sCharsToGsmTables[languageTable];
        SparseIntArray charToShiftTable = sCharsToShiftTables[languageShiftTable];
        for (int i = 0, septets = startingSeptetOffset, bitOffset = startingSeptetOffset * 7;
                 i < dataLen && septets < septetCount;
                 i++, bitOffset += 7) {
            char c = data.charAt(i);
            int v = charToLanguageTable.get(c, -1);
            if (v == -1) {
                v = charToShiftTable.get(c, -1);  // Lookup the extended char.
                if (v == -1) {
                    if (throwException) {
                        throw new EncodeException("stringToGsm7BitPacked(): unencodable char");
                    } else {
                        v = charToLanguageTable.get(' ", ' ");   // should return ASCII space
                    }
                } else {
                    packSmsChar(ret, bitOffset, GSM_EXTENDED_ESCAPE);
                    bitOffset += 7;
                    septets++;
                }
            }
            packSmsChar(ret, bitOffset, v);
            septets++;
        }
        ret[0] = (byte) (septetCount);  // Validated by check above.
        return ret;
    
public static byte[]stringToGsm7BitPacked(java.lang.String data)
Converts a String into a byte array containing the 7-bit packed GSM Alphabet representation of the string. Unencodable chars are encoded as spaces Byte 0 in the returned byte array is the count of septets used The returned byte array is the minimum size required to store the packed septets. The returned array cannot contain more than 255 septets.

param
data the data string to encode
return
the encoded string
throws
EncodeException if String is too large to encode

        return stringToGsm7BitPacked(data, 0, true, 0, 0);
    
public static byte[]stringToGsm7BitPackedWithHeader(java.lang.String data, byte[] header)
Converts a String into a byte array containing the 7-bit packed GSM Alphabet representation of the string. If a header is provided, this is included in the returned byte array and padded to a septet boundary. This method is used by OEM code.

param
data The text string to encode.
param
header Optional header (including length byte) that precedes the encoded data, padded to septet boundary.
return
Byte array containing header and encoded data.
throws
EncodeException if String is too large to encode
see
#stringToGsm7BitPackedWithHeader(String, byte[], int, int)

        return stringToGsm7BitPackedWithHeader(data, header, 0, 0);
    
public static byte[]stringToGsm7BitPackedWithHeader(java.lang.String data, byte[] header, int languageTable, int languageShiftTable)
Converts a String into a byte array containing the 7-bit packed GSM Alphabet representation of the string. If a header is provided, this is included in the returned byte array and padded to a septet boundary. Unencodable chars are encoded as spaces Byte 0 in the returned byte array is the count of septets used, including the header and header padding. The returned byte array is the minimum size required to store the packed septets. The returned array cannot contain more than 255 septets.

param
data The text string to encode.
param
header Optional header (including length byte) that precedes the encoded data, padded to septet boundary.
param
languageTable the 7 bit language table, or 0 for the default GSM alphabet
param
languageShiftTable the 7 bit single shift language table, or 0 for the default GSM extension table
return
Byte array containing header and encoded data.
throws
EncodeException if String is too large to encode

        if (header == null || header.length == 0) {
            return stringToGsm7BitPacked(data, languageTable, languageShiftTable);
        }

        int headerBits = (header.length + 1) * 8;
        int headerSeptets = (headerBits + 6) / 7;

        byte[] ret = stringToGsm7BitPacked(data, headerSeptets, true, languageTable,
                languageShiftTable);

        // Paste in the header
        ret[1] = (byte)header.length;
        System.arraycopy(header, 0, ret, 2, header.length);
        return ret;
    
public static byte[]stringToGsm8BitPacked(java.lang.String s)
Convert a string into an 8-bit unpacked GSM alphabet byte array. Always uses GSM default 7-bit alphabet and extension table.

param
s the string to encode
return
the 8-bit GSM encoded byte array for the string

        byte[] ret;

        int septets = countGsmSeptetsUsingTables(s, true, 0, 0);

        // Enough for all the septets and the length byte prefix
        ret = new byte[septets];

        stringToGsm8BitUnpackedField(s, ret, 0, ret.length);

        return ret;
    
public static voidstringToGsm8BitUnpackedField(java.lang.String s, byte[] dest, int offset, int length)
Write a String into a GSM 8-bit unpacked field of Field is padded with 0xff's, string is truncated if necessary

param
s the string to encode
param
dest the destination byte array
param
offset the starting offset for the encoded string
param
length the maximum number of bytes to write

        int outByteIndex = offset;
        SparseIntArray charToLanguageTable = sCharsToGsmTables[0];
        SparseIntArray charToShiftTable = sCharsToShiftTables[0];

        // Septets are stored in byte-aligned octets
        for (int i = 0, sz = s.length()
                ; i < sz && (outByteIndex - offset) < length
                ; i++
        ) {
            char c = s.charAt(i);

            int v = charToLanguageTable.get(c, -1);

            if (v == -1) {
                v = charToShiftTable.get(c, -1);
                if (v == -1) {
                    v = charToLanguageTable.get(' ", ' ");  // fall back to ASCII space
                } else {
                    // make sure we can fit an escaped char
                    if (! (outByteIndex + 1 - offset < length)) {
                        break;
                    }

                    dest[outByteIndex++] = GSM_EXTENDED_ESCAPE;
                }
            }

            dest[outByteIndex++] = (byte)v;
        }

        // pad with 0xff's
        while((outByteIndex - offset) < length) {
            dest[outByteIndex++] = (byte)0xff;
        }