10825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/*
20825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Copyright (C) 2012 The Android Open Source Project
30825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
40825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Licensed under the Apache License, Version 2.0 (the "License");
50825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * you may not use this file except in compliance with the License.
60825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * You may obtain a copy of the License at
70825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
80825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *      http://www.apache.org/licenses/LICENSE-2.0
90825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Unless required by applicable law or agreed to in writing, software
110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * distributed under the License is distributed on an "AS IS" BASIS,
120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * See the License for the specific language governing permissions and
140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * limitations under the License.
150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
170825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepackage com.android.internal.telephony.gsm;
180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
190825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.SmsCbLocation;
200825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.SmsCbMessage;
210825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.util.Pair;
220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
230825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.GsmAlphabet;
240825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.SmsConstants;
250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
260825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.io.UnsupportedEncodingException;
270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/**
290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
320825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepublic class GsmSmsCbMessage {
330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /**
350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     */
370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final String[] LANGUAGE_CODES_GROUP_0 = {
380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            "pl", null
400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    };
410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /**
430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     */
450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final String[] LANGUAGE_CODES_GROUP_2 = {
460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            null, null
480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    };
490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final char CARRIAGE_RETURN = 0x0d;
510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int PDU_BODY_PAGE_LENGTH = 82;
530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /** Utility class with only static methods. */
550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private GsmSmsCbMessage() { }
560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /**
580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     *
600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param pdus PDU bytes
610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     */
623a96b0ee1d152549279c58ba24a0a035cc9a557bAmit Mahajan    public static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            byte[][] pdus) throws IllegalArgumentException {
640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (header.isEtwsPrimaryNotification()) {
65db2e012f5843255064f927f323bde20cdc2d7ef6Jack Yu            // ETSI TS 23.041 ETWS Primary Notification message
66db2e012f5843255064f927f323bde20cdc2d7ef6Jack Yu            // ETWS primary message only contains 4 fields including serial number,
67db2e012f5843255064f927f323bde20cdc2d7ef6Jack Yu            // message identifier, warning type, and warning security information.
683a96b0ee1d152549279c58ba24a0a035cc9a557bAmit Mahajan            // There is no field for the content/text. We hardcode "ETWS" in the
69db2e012f5843255064f927f323bde20cdc2d7ef6Jack Yu            // text body so the user won't see an empty dialog without any text.
700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    header.getGeographicalScope(), header.getSerialNumber(),
720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    location, header.getServiceCategory(),
730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    header.getEtwsInfo(), header.getCmasInfo());
750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String language = null;
770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            StringBuilder sb = new StringBuilder();
780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (byte[] pdu : pdus) {
790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Pair<String, String> p = parseBody(header, pdu);
800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                language = p.first;
810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                sb.append(p.second);
820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    header.getGeographicalScope(), header.getSerialNumber(), location,
880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    header.getServiceCategory(), language, sb.toString(), priority,
890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    header.getEtwsInfo(), header.getCmasInfo());
900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /**
940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * Create a new SmsCbMessage object from one or more received PDUs. This is used by some
950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     *
970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param location the location (geographical scope) for the message
980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param pdus PDU bytes
990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     */
1000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
1010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throws IllegalArgumentException {
1020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        SmsCbHeader header = new SmsCbHeader(pdus[0]);
1030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return createSmsCbMessage(header, location, pdus);
1040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /**
1070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * Parse and unpack the body text according to the encoding in the DCS.
1080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * After completing successfully this method will have assigned the body
1090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * text into mBody, and optionally the language code into mLanguage
1100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     *
1110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param header the message header to use
1120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param pdu the PDU to decode
1130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @return a Pair of Strings containing the language and body of the message
1140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     */
1150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
1160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        int encoding;
1170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String language = null;
1180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean hasLanguageIndicator = false;
1190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        int dataCodingScheme = header.getDataCodingScheme();
1200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
1220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // section 5.
1230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        switch ((dataCodingScheme & 0xf0) >> 4) {
1240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x00:
1250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                encoding = SmsConstants.ENCODING_7BIT;
1260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
1270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x01:
1300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                hasLanguageIndicator = true;
1310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if ((dataCodingScheme & 0x0f) == 0x01) {
1320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    encoding = SmsConstants.ENCODING_16BIT;
1330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
1340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    encoding = SmsConstants.ENCODING_7BIT;
1350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x02:
1390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                encoding = SmsConstants.ENCODING_7BIT;
1400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
1410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x03:
1440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                encoding = SmsConstants.ENCODING_7BIT;
1450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x04:
1480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x05:
1490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                switch ((dataCodingScheme & 0x0c) >> 2) {
1500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 0x01:
1510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        encoding = SmsConstants.ENCODING_8BIT;
1520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
1530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 0x02:
1550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        encoding = SmsConstants.ENCODING_16BIT;
1560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
1570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 0x00:
1590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    default:
1600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        encoding = SmsConstants.ENCODING_7BIT;
1610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
1620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x06:
1660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x07:
1670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Compression not supported
1680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x09:
1690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // UDH structure not supported
1700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x0e:
1710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Defined by the WAP forum not supported
1720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
1730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + dataCodingScheme);
1740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case 0x0f:
1760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
1770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    encoding = SmsConstants.ENCODING_8BIT;
1780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
1790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    encoding = SmsConstants.ENCODING_7BIT;
1800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            default:
1840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Reserved values are to be treated as 7-bit
1850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                encoding = SmsConstants.ENCODING_7BIT;
1860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
1870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (header.isUmtsFormat()) {
1900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Payload may contain multiple pages
1910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
1920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
1940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    * nrPages) {
1950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
1960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + nrPages + " pages");
1970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            StringBuilder sb = new StringBuilder();
2000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (int i = 0; i < nrPages; i++) {
2020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Each page is 82 bytes followed by a length octet indicating
2030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // the number of useful octets within those 82
2040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
2050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
2060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (length > PDU_BODY_PAGE_LENGTH) {
2080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new IllegalArgumentException("Page length " + length
2090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
2100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
2110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
2130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        hasLanguageIndicator, language);
2140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                language = p.first;
2150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                sb.append(p.second);
2160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return new Pair<String, String>(language, sb.toString());
2180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
2190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Payload is one single page
2200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int offset = SmsCbHeader.PDU_HEADER_LENGTH;
2210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int length = pdu.length - offset;
2220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
2240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    /**
2280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * Unpack body text from the pdu using the given encoding, position and
2290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * length within the pdu
2300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     *
2310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param pdu The pdu
2320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param encoding The encoding, as derived from the DCS
2330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param offset Position of the first byte to unpack
2340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param length Number of bytes to unpack
2350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param hasLanguageIndicator true if the body text is preceded by a
2360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     *            language indicator. If so, this method will as a side-effect
2370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     *            assign the extracted language code into mLanguage
2380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @param language the language to return if hasLanguageIndicator is false
2390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     * @return a Pair of Strings containing the language and body of the message
2400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville     */
2410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
2420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            boolean hasLanguageIndicator, String language) {
2430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String body = null;
2440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        switch (encoding) {
2460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SmsConstants.ENCODING_7BIT:
2470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
2480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (hasLanguageIndicator && body != null && body.length() > 2) {
2500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    // Language is two GSM characters followed by a CR.
2510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    // The actual body text is offset by 3 characters.
2520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    language = body.substring(0, 2);
2530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    body = body.substring(3);
2540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
2550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
2560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            case SmsConstants.ENCODING_16BIT:
2580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (hasLanguageIndicator && pdu.length >= offset + 2) {
2590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    // Language is two GSM characters.
2600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    // The actual body text is offset by 2 bytes.
2610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
2620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    offset += 2;
2630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    length -= 2;
2640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
2650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                try {
2670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    body = new String(pdu, offset, (length & 0xfffe), "utf-16");
2680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } catch (UnsupportedEncodingException e) {
2690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    // Apparently it wasn't valid UTF-16.
2700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new IllegalArgumentException("Error decoding UTF-16 message", e);
2710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
2720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
2730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            default:
2750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                break;
2760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (body != null) {
2790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Remove trailing carriage return
2800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (int i = body.length() - 1; i >= 0; i--) {
2810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (body.charAt(i) != CARRIAGE_RETURN) {
2820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    body = body.substring(0, i + 1);
2830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
2840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
2850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
2870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            body = "";
2880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return new Pair<String, String>(language, body);
2910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville}
293