Home | History | Annotate | Download | only in gsm
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.telephony.gsm;
     18 
     19 import android.telephony.PhoneNumberUtils;
     20 import android.text.format.Time;
     21 import android.telephony.Rlog;
     22 
     23 import com.android.internal.telephony.EncodeException;
     24 import com.android.internal.telephony.GsmAlphabet;
     25 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
     26 import com.android.internal.telephony.uicc.IccUtils;
     27 import com.android.internal.telephony.SmsHeader;
     28 import com.android.internal.telephony.SmsMessageBase;
     29 
     30 import java.io.ByteArrayOutputStream;
     31 import java.io.UnsupportedEncodingException;
     32 import java.text.ParseException;
     33 
     34 import static com.android.internal.telephony.SmsConstants.MessageClass;
     35 import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
     36 import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
     37 import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
     38 import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
     39 import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
     40 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
     41 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
     42 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
     43 
     44 /**
     45  * A Short Message Service message.
     46  *
     47  */
     48 public class SmsMessage extends SmsMessageBase {
     49     static final String LOG_TAG = "SmsMessage";
     50     private static final boolean VDBG = false;
     51 
     52     private MessageClass messageClass;
     53 
     54     /**
     55      * TP-Message-Type-Indicator
     56      * 9.2.3
     57      */
     58     private int mMti;
     59 
     60     /** TP-Protocol-Identifier (TP-PID) */
     61     private int mProtocolIdentifier;
     62 
     63     // TP-Data-Coding-Scheme
     64     // see TS 23.038
     65     private int mDataCodingScheme;
     66 
     67     // TP-Reply-Path
     68     // e.g. 23.040 9.2.2.1
     69     private boolean mReplyPathPresent = false;
     70 
     71     /** The address of the receiver. */
     72     private GsmSmsAddress mRecipientAddress;
     73 
     74     /**
     75      *  TP-Status - status of a previously submitted SMS.
     76      *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
     77      *  see TS 23.040, 9.2.3.15 for description of other possible values.
     78      */
     79     private int mStatus;
     80 
     81     /**
     82      *  TP-Status - status of a previously submitted SMS.
     83      *  This field is true iff the message is a SMS-STATUS-REPORT message.
     84      */
     85     private boolean mIsStatusReportMessage = false;
     86 
     87     public static class SubmitPdu extends SubmitPduBase {
     88     }
     89 
     90     /**
     91      * Create an SmsMessage from a raw PDU.
     92      */
     93     public static SmsMessage createFromPdu(byte[] pdu) {
     94         try {
     95             SmsMessage msg = new SmsMessage();
     96             msg.parsePdu(pdu);
     97             return msg;
     98         } catch (RuntimeException ex) {
     99             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
    100             return null;
    101         } catch (OutOfMemoryError e) {
    102             Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
    103             return null;
    104         }
    105     }
    106 
    107     /**
    108      * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
    109      * by TP_PID field set to value 0x40
    110      */
    111     public boolean isTypeZero() {
    112         return (mProtocolIdentifier == 0x40);
    113     }
    114 
    115     /**
    116      * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
    117      * +CMT unsolicited response (PDU mode, of course)
    118      *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
    119      *
    120      * Only public for debugging
    121      *
    122      * {@hide}
    123      */
    124     public static SmsMessage newFromCMT(String[] lines) {
    125         try {
    126             SmsMessage msg = new SmsMessage();
    127             msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
    128             return msg;
    129         } catch (RuntimeException ex) {
    130             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
    131             return null;
    132         }
    133     }
    134 
    135     /** @hide */
    136     public static SmsMessage newFromCDS(String line) {
    137         try {
    138             SmsMessage msg = new SmsMessage();
    139             msg.parsePdu(IccUtils.hexStringToBytes(line));
    140             return msg;
    141         } catch (RuntimeException ex) {
    142             Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
    143             return null;
    144         }
    145     }
    146 
    147     /**
    148      * Create an SmsMessage from an SMS EF record.
    149      *
    150      * @param index Index of SMS record. This should be index in ArrayList
    151      *              returned by SmsManager.getAllMessagesFromSim + 1.
    152      * @param data Record data.
    153      * @return An SmsMessage representing the record.
    154      *
    155      * @hide
    156      */
    157     public static SmsMessage createFromEfRecord(int index, byte[] data) {
    158         try {
    159             SmsMessage msg = new SmsMessage();
    160 
    161             msg.mIndexOnIcc = index;
    162 
    163             // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
    164             // or STORED_UNSENT
    165             // See TS 51.011 10.5.3
    166             if ((data[0] & 1) == 0) {
    167                 Rlog.w(LOG_TAG,
    168                         "SMS parsing failed: Trying to parse a free record");
    169                 return null;
    170             } else {
    171                 msg.mStatusOnIcc = data[0] & 0x07;
    172             }
    173 
    174             int size = data.length - 1;
    175 
    176             // Note: Data may include trailing FF's.  That's OK; message
    177             // should still parse correctly.
    178             byte[] pdu = new byte[size];
    179             System.arraycopy(data, 1, pdu, 0, size);
    180             msg.parsePdu(pdu);
    181             return msg;
    182         } catch (RuntimeException ex) {
    183             Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
    184             return null;
    185         }
    186     }
    187 
    188     /**
    189      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
    190      * length in bytes (not hex chars) less the SMSC header
    191      */
    192     public static int getTPLayerLengthForPDU(String pdu) {
    193         int len = pdu.length() / 2;
    194         int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
    195 
    196         return len - smscLen - 1;
    197     }
    198 
    199     /**
    200      * Get an SMS-SUBMIT PDU for a destination address and a message
    201      *
    202      * @param scAddress Service Centre address.  Null means use default.
    203      * @return a <code>SubmitPdu</code> containing the encoded SC
    204      *         address, if applicable, and the encoded message.
    205      *         Returns null on encode error.
    206      * @hide
    207      */
    208     public static SubmitPdu getSubmitPdu(String scAddress,
    209             String destinationAddress, String message,
    210             boolean statusReportRequested, byte[] header) {
    211         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
    212                 ENCODING_UNKNOWN, 0, 0);
    213     }
    214 
    215 
    216     /**
    217      * Get an SMS-SUBMIT PDU for a destination address and a message using the
    218      * specified encoding.
    219      *
    220      * @param scAddress Service Centre address.  Null means use default.
    221      * @param encoding Encoding defined by constants in
    222      *        com.android.internal.telephony.SmsConstants.ENCODING_*
    223      * @param languageTable
    224      * @param languageShiftTable
    225      * @return a <code>SubmitPdu</code> containing the encoded SC
    226      *         address, if applicable, and the encoded message.
    227      *         Returns null on encode error.
    228      * @hide
    229      */
    230     public static SubmitPdu getSubmitPdu(String scAddress,
    231             String destinationAddress, String message,
    232             boolean statusReportRequested, byte[] header, int encoding,
    233             int languageTable, int languageShiftTable) {
    234 
    235         // Perform null parameter checks.
    236         if (message == null || destinationAddress == null) {
    237             return null;
    238         }
    239 
    240         if (encoding == ENCODING_UNKNOWN) {
    241             // Find the best encoding to use
    242             TextEncodingDetails ted = calculateLength(message, false);
    243             encoding = ted.codeUnitSize;
    244             languageTable = ted.languageTable;
    245             languageShiftTable = ted.languageShiftTable;
    246 
    247             if (encoding == ENCODING_7BIT &&
    248                     (languageTable != 0 || languageShiftTable != 0)) {
    249                 if (header != null) {
    250                     SmsHeader smsHeader = SmsHeader.fromByteArray(header);
    251                     if (smsHeader.languageTable != languageTable
    252                             || smsHeader.languageShiftTable != languageShiftTable) {
    253                         Rlog.w(LOG_TAG, "Updating language table in SMS header: "
    254                                 + smsHeader.languageTable + " -> " + languageTable + ", "
    255                                 + smsHeader.languageShiftTable + " -> " + languageShiftTable);
    256                         smsHeader.languageTable = languageTable;
    257                         smsHeader.languageShiftTable = languageShiftTable;
    258                         header = SmsHeader.toByteArray(smsHeader);
    259                     }
    260                 } else {
    261                     SmsHeader smsHeader = new SmsHeader();
    262                     smsHeader.languageTable = languageTable;
    263                     smsHeader.languageShiftTable = languageShiftTable;
    264                     header = SmsHeader.toByteArray(smsHeader);
    265                 }
    266             }
    267         }
    268 
    269         SubmitPdu ret = new SubmitPdu();
    270         // MTI = SMS-SUBMIT, UDHI = header != null
    271         byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
    272         ByteArrayOutputStream bo = getSubmitPduHead(
    273                 scAddress, destinationAddress, mtiByte,
    274                 statusReportRequested, ret);
    275 
    276         // User Data (and length)
    277         byte[] userData;
    278         try {
    279             if (encoding == ENCODING_7BIT) {
    280                 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
    281                         languageTable, languageShiftTable);
    282             } else { //assume UCS-2
    283                 try {
    284                     userData = encodeUCS2(message, header);
    285                 } catch(UnsupportedEncodingException uex) {
    286                     Rlog.e(LOG_TAG,
    287                             "Implausible UnsupportedEncodingException ",
    288                             uex);
    289                     return null;
    290                 }
    291             }
    292         } catch (EncodeException ex) {
    293             // Encoding to the 7-bit alphabet failed. Let's see if we can
    294             // send it as a UCS-2 encoded message
    295             try {
    296                 userData = encodeUCS2(message, header);
    297                 encoding = ENCODING_16BIT;
    298             } catch(UnsupportedEncodingException uex) {
    299                 Rlog.e(LOG_TAG,
    300                         "Implausible UnsupportedEncodingException ",
    301                         uex);
    302                 return null;
    303             }
    304         }
    305 
    306         if (encoding == ENCODING_7BIT) {
    307             if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
    308                 // Message too long
    309                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
    310                 return null;
    311             }
    312             // TP-Data-Coding-Scheme
    313             // Default encoding, uncompressed
    314             // To test writing messages to the SIM card, change this value 0x00
    315             // to 0x12, which means "bits 1 and 0 contain message class, and the
    316             // class is 2". Note that this takes effect for the sender. In other
    317             // words, messages sent by the phone with this change will end up on
    318             // the receiver's SIM card. You can then send messages to yourself
    319             // (on a phone with this change) and they'll end up on the SIM card.
    320             bo.write(0x00);
    321         } else { // assume UCS-2
    322             if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
    323                 // Message too long
    324                 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
    325                 return null;
    326             }
    327             // TP-Data-Coding-Scheme
    328             // UCS-2 encoding, uncompressed
    329             bo.write(0x08);
    330         }
    331 
    332         // (no TP-Validity-Period)
    333         bo.write(userData, 0, userData.length);
    334         ret.encodedMessage = bo.toByteArray();
    335         return ret;
    336     }
    337 
    338     /**
    339      * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
    340      *
    341      * @return encoded message as UCS2
    342      * @throws UnsupportedEncodingException
    343      */
    344     private static byte[] encodeUCS2(String message, byte[] header)
    345         throws UnsupportedEncodingException {
    346         byte[] userData, textPart;
    347         textPart = message.getBytes("utf-16be");
    348 
    349         if (header != null) {
    350             // Need 1 byte for UDHL
    351             userData = new byte[header.length + textPart.length + 1];
    352 
    353             userData[0] = (byte)header.length;
    354             System.arraycopy(header, 0, userData, 1, header.length);
    355             System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
    356         }
    357         else {
    358             userData = textPart;
    359         }
    360         byte[] ret = new byte[userData.length+1];
    361         ret[0] = (byte) (userData.length & 0xff );
    362         System.arraycopy(userData, 0, ret, 1, userData.length);
    363         return ret;
    364     }
    365 
    366     /**
    367      * Get an SMS-SUBMIT PDU for a destination address and a message
    368      *
    369      * @param scAddress Service Centre address.  Null means use default.
    370      * @return a <code>SubmitPdu</code> containing the encoded SC
    371      *         address, if applicable, and the encoded message.
    372      *         Returns null on encode error.
    373      */
    374     public static SubmitPdu getSubmitPdu(String scAddress,
    375             String destinationAddress, String message,
    376             boolean statusReportRequested) {
    377 
    378         return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
    379     }
    380 
    381     /**
    382      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
    383      *
    384      * @param scAddress Service Centre address. null == use default
    385      * @param destinationAddress the address of the destination for the message
    386      * @param destinationPort the port to deliver the message to at the
    387      *        destination
    388      * @param data the data for the message
    389      * @return a <code>SubmitPdu</code> containing the encoded SC
    390      *         address, if applicable, and the encoded message.
    391      *         Returns null on encode error.
    392      */
    393     public static SubmitPdu getSubmitPdu(String scAddress,
    394             String destinationAddress, int destinationPort, byte[] data,
    395             boolean statusReportRequested) {
    396 
    397         SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
    398         portAddrs.destPort = destinationPort;
    399         portAddrs.origPort = 0;
    400         portAddrs.areEightBits = false;
    401 
    402         SmsHeader smsHeader = new SmsHeader();
    403         smsHeader.portAddrs = portAddrs;
    404 
    405         byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
    406 
    407         if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
    408             Rlog.e(LOG_TAG, "SMS data message may only contain "
    409                     + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
    410             return null;
    411         }
    412 
    413         SubmitPdu ret = new SubmitPdu();
    414         ByteArrayOutputStream bo = getSubmitPduHead(
    415                 scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
    416                                                             // TP-UDHI = true
    417                 statusReportRequested, ret);
    418 
    419         // TP-Data-Coding-Scheme
    420         // No class, 8 bit data
    421         bo.write(0x04);
    422 
    423         // (no TP-Validity-Period)
    424 
    425         // Total size
    426         bo.write(data.length + smsHeaderData.length + 1);
    427 
    428         // User data header
    429         bo.write(smsHeaderData.length);
    430         bo.write(smsHeaderData, 0, smsHeaderData.length);
    431 
    432         // User data
    433         bo.write(data, 0, data.length);
    434 
    435         ret.encodedMessage = bo.toByteArray();
    436         return ret;
    437     }
    438 
    439     /**
    440      * Create the beginning of a SUBMIT PDU.  This is the part of the
    441      * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
    442      * one of which takes a byte array and the other of which takes a
    443      * <code>String</code>.
    444      *
    445      * @param scAddress Service Centre address. null == use default
    446      * @param destinationAddress the address of the destination for the message
    447      * @param mtiByte
    448      * @param ret <code>SubmitPdu</code> containing the encoded SC
    449      *        address, if applicable, and the encoded message
    450      */
    451     private static ByteArrayOutputStream getSubmitPduHead(
    452             String scAddress, String destinationAddress, byte mtiByte,
    453             boolean statusReportRequested, SubmitPdu ret) {
    454         ByteArrayOutputStream bo = new ByteArrayOutputStream(
    455                 MAX_USER_DATA_BYTES + 40);
    456 
    457         // SMSC address with length octet, or 0
    458         if (scAddress == null) {
    459             ret.encodedScAddress = null;
    460         } else {
    461             ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
    462                     scAddress);
    463         }
    464 
    465         // TP-Message-Type-Indicator (and friends)
    466         if (statusReportRequested) {
    467             // Set TP-Status-Report-Request bit.
    468             mtiByte |= 0x20;
    469             if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
    470         }
    471         bo.write(mtiByte);
    472 
    473         // space for TP-Message-Reference
    474         bo.write(0);
    475 
    476         byte[] daBytes;
    477 
    478         daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
    479 
    480         // destination address length in BCD digits, ignoring TON byte and pad
    481         // TODO Should be better.
    482         bo.write((daBytes.length - 1) * 2
    483                 - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
    484 
    485         // destination address
    486         bo.write(daBytes, 0, daBytes.length);
    487 
    488         // TP-Protocol-Identifier
    489         bo.write(0);
    490         return bo;
    491     }
    492 
    493     private static class PduParser {
    494         byte mPdu[];
    495         int mCur;
    496         SmsHeader mUserDataHeader;
    497         byte[] mUserData;
    498         int mUserDataSeptetPadding;
    499 
    500         PduParser(byte[] pdu) {
    501             mPdu = pdu;
    502             mCur = 0;
    503             mUserDataSeptetPadding = 0;
    504         }
    505 
    506         /**
    507          * Parse and return the SC address prepended to SMS messages coming via
    508          * the TS 27.005 / AT interface.  Returns null on invalid address
    509          */
    510         String getSCAddress() {
    511             int len;
    512             String ret;
    513 
    514             // length of SC Address
    515             len = getByte();
    516 
    517             if (len == 0) {
    518                 // no SC address
    519                 ret = null;
    520             } else {
    521                 // SC address
    522                 try {
    523                     ret = PhoneNumberUtils
    524                             .calledPartyBCDToString(mPdu, mCur, len);
    525                 } catch (RuntimeException tr) {
    526                     Rlog.d(LOG_TAG, "invalid SC address: ", tr);
    527                     ret = null;
    528                 }
    529             }
    530 
    531             mCur += len;
    532 
    533             return ret;
    534         }
    535 
    536         /**
    537          * returns non-sign-extended byte value
    538          */
    539         int getByte() {
    540             return mPdu[mCur++] & 0xff;
    541         }
    542 
    543         /**
    544          * Any address except the SC address (eg, originating address) See TS
    545          * 23.040 9.1.2.5
    546          */
    547         GsmSmsAddress getAddress() {
    548             GsmSmsAddress ret;
    549 
    550             // "The Address-Length field is an integer representation of
    551             // the number field, i.e. excludes any semi-octet containing only
    552             // fill bits."
    553             // The TOA field is not included as part of this
    554             int addressLength = mPdu[mCur] & 0xff;
    555             int lengthBytes = 2 + (addressLength + 1) / 2;
    556 
    557             try {
    558                 ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
    559             } catch (ParseException e) {
    560                 ret = null;
    561                 //This is caught by createFromPdu(byte[] pdu)
    562                 throw new RuntimeException(e.getMessage());
    563             }
    564 
    565             mCur += lengthBytes;
    566 
    567             return ret;
    568         }
    569 
    570         /**
    571          * Parses an SC timestamp and returns a currentTimeMillis()-style
    572          * timestamp
    573          */
    574 
    575         long getSCTimestampMillis() {
    576             // TP-Service-Centre-Time-Stamp
    577             int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
    578             int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
    579             int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
    580             int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
    581             int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
    582             int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
    583 
    584             // For the timezone, the most significant bit of the
    585             // least significant nibble is the sign byte
    586             // (meaning the max range of this field is 79 quarter-hours,
    587             // which is more than enough)
    588 
    589             byte tzByte = mPdu[mCur++];
    590 
    591             // Mask out sign bit.
    592             int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
    593 
    594             timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
    595 
    596             Time time = new Time(Time.TIMEZONE_UTC);
    597 
    598             // It's 2006.  Should I really support years < 2000?
    599             time.year = year >= 90 ? year + 1900 : year + 2000;
    600             time.month = month - 1;
    601             time.monthDay = day;
    602             time.hour = hour;
    603             time.minute = minute;
    604             time.second = second;
    605 
    606             // Timezone offset is in quarter hours.
    607             return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
    608         }
    609 
    610         /**
    611          * Pulls the user data out of the PDU, and separates the payload from
    612          * the header if there is one.
    613          *
    614          * @param hasUserDataHeader true if there is a user data header
    615          * @param dataInSeptets true if the data payload is in septets instead
    616          *  of octets
    617          * @return the number of septets or octets in the user data payload
    618          */
    619         int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
    620             int offset = mCur;
    621             int userDataLength = mPdu[offset++] & 0xff;
    622             int headerSeptets = 0;
    623             int userDataHeaderLength = 0;
    624 
    625             if (hasUserDataHeader) {
    626                 userDataHeaderLength = mPdu[offset++] & 0xff;
    627 
    628                 byte[] udh = new byte[userDataHeaderLength];
    629                 System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
    630                 mUserDataHeader = SmsHeader.fromByteArray(udh);
    631                 offset += userDataHeaderLength;
    632 
    633                 int headerBits = (userDataHeaderLength + 1) * 8;
    634                 headerSeptets = headerBits / 7;
    635                 headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
    636                 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
    637             }
    638 
    639             int bufferLen;
    640             if (dataInSeptets) {
    641                 /*
    642                  * Here we just create the user data length to be the remainder of
    643                  * the pdu minus the user data header, since userDataLength means
    644                  * the number of uncompressed septets.
    645                  */
    646                 bufferLen = mPdu.length - offset;
    647             } else {
    648                 /*
    649                  * userDataLength is the count of octets, so just subtract the
    650                  * user data header.
    651                  */
    652                 bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
    653                 if (bufferLen < 0) {
    654                     bufferLen = 0;
    655                 }
    656             }
    657 
    658             mUserData = new byte[bufferLen];
    659             System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
    660             mCur = offset;
    661 
    662             if (dataInSeptets) {
    663                 // Return the number of septets
    664                 int count = userDataLength - headerSeptets;
    665                 // If count < 0, return 0 (means UDL was probably incorrect)
    666                 return count < 0 ? 0 : count;
    667             } else {
    668                 // Return the number of octets
    669                 return mUserData.length;
    670             }
    671         }
    672 
    673         /**
    674          * Returns the user data payload, not including the headers
    675          *
    676          * @return the user data payload, not including the headers
    677          */
    678         byte[] getUserData() {
    679             return mUserData;
    680         }
    681 
    682         /**
    683          * Returns an object representing the user data headers
    684          *
    685          * {@hide}
    686          */
    687         SmsHeader getUserDataHeader() {
    688             return mUserDataHeader;
    689         }
    690 
    691         /**
    692          * Interprets the user data payload as packed GSM 7bit characters, and
    693          * decodes them into a String.
    694          *
    695          * @param septetCount the number of septets in the user data payload
    696          * @return a String with the decoded characters
    697          */
    698         String getUserDataGSM7Bit(int septetCount, int languageTable,
    699                 int languageShiftTable) {
    700             String ret;
    701 
    702             ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
    703                     mUserDataSeptetPadding, languageTable, languageShiftTable);
    704 
    705             mCur += (septetCount * 7) / 8;
    706 
    707             return ret;
    708         }
    709 
    710         /**
    711          * Interprets the user data payload as UCS2 characters, and
    712          * decodes them into a String.
    713          *
    714          * @param byteCount the number of bytes in the user data payload
    715          * @return a String with the decoded characters
    716          */
    717         String getUserDataUCS2(int byteCount) {
    718             String ret;
    719 
    720             try {
    721                 ret = new String(mPdu, mCur, byteCount, "utf-16");
    722             } catch (UnsupportedEncodingException ex) {
    723                 ret = "";
    724                 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
    725             }
    726 
    727             mCur += byteCount;
    728             return ret;
    729         }
    730 
    731         /**
    732          * Interprets the user data payload as KSC-5601 characters, and
    733          * decodes them into a String.
    734          *
    735          * @param byteCount the number of bytes in the user data payload
    736          * @return a String with the decoded characters
    737          */
    738         String getUserDataKSC5601(int byteCount) {
    739             String ret;
    740 
    741             try {
    742                 ret = new String(mPdu, mCur, byteCount, "KSC5601");
    743             } catch (UnsupportedEncodingException ex) {
    744                 ret = "";
    745                 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
    746             }
    747 
    748             mCur += byteCount;
    749             return ret;
    750         }
    751 
    752         boolean moreDataPresent() {
    753             return (mPdu.length > mCur);
    754         }
    755     }
    756 
    757     /**
    758      * Calculate the number of septets needed to encode the message.
    759      *
    760      * @param msgBody the message to encode
    761      * @param use7bitOnly ignore (but still count) illegal characters if true
    762      * @return TextEncodingDetails
    763      */
    764     public static TextEncodingDetails calculateLength(CharSequence msgBody,
    765             boolean use7bitOnly) {
    766         TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly);
    767         if (ted == null) {
    768             ted = new TextEncodingDetails();
    769             int octets = msgBody.length() * 2;
    770             ted.codeUnitCount = msgBody.length();
    771             if (octets > MAX_USER_DATA_BYTES) {
    772                 ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
    773                         MAX_USER_DATA_BYTES_WITH_HEADER;
    774                 ted.codeUnitsRemaining = ((ted.msgCount *
    775                         MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
    776             } else {
    777                 ted.msgCount = 1;
    778                 ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2;
    779             }
    780             ted.codeUnitSize = ENCODING_16BIT;
    781         }
    782         return ted;
    783     }
    784 
    785     /** {@inheritDoc} */
    786     @Override
    787     public int getProtocolIdentifier() {
    788         return mProtocolIdentifier;
    789     }
    790 
    791     /**
    792      * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
    793      * @return the TP-DCS field of the SMS header
    794      */
    795     int getDataCodingScheme() {
    796         return mDataCodingScheme;
    797     }
    798 
    799     /** {@inheritDoc} */
    800     @Override
    801     public boolean isReplace() {
    802         return (mProtocolIdentifier & 0xc0) == 0x40
    803                 && (mProtocolIdentifier & 0x3f) > 0
    804                 && (mProtocolIdentifier & 0x3f) < 8;
    805     }
    806 
    807     /** {@inheritDoc} */
    808     @Override
    809     public boolean isCphsMwiMessage() {
    810         return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
    811                 || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
    812     }
    813 
    814     /** {@inheritDoc} */
    815     @Override
    816     public boolean isMWIClearMessage() {
    817         if (mIsMwi && !mMwiSense) {
    818             return true;
    819         }
    820 
    821         return mOriginatingAddress != null
    822                 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
    823     }
    824 
    825     /** {@inheritDoc} */
    826     @Override
    827     public boolean isMWISetMessage() {
    828         if (mIsMwi && mMwiSense) {
    829             return true;
    830         }
    831 
    832         return mOriginatingAddress != null
    833                 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
    834     }
    835 
    836     /** {@inheritDoc} */
    837     @Override
    838     public boolean isMwiDontStore() {
    839         if (mIsMwi && mMwiDontStore) {
    840             return true;
    841         }
    842 
    843         if (isCphsMwiMessage()) {
    844             // See CPHS 4.2 Section B.4.2.1
    845             // If the user data is a single space char, do not store
    846             // the message. Otherwise, store and display as usual
    847             if (" ".equals(getMessageBody())) {
    848                 return true;
    849             }
    850         }
    851 
    852         return false;
    853     }
    854 
    855     /** {@inheritDoc} */
    856     @Override
    857     public int getStatus() {
    858         return mStatus;
    859     }
    860 
    861     /** {@inheritDoc} */
    862     @Override
    863     public boolean isStatusReportMessage() {
    864         return mIsStatusReportMessage;
    865     }
    866 
    867     /** {@inheritDoc} */
    868     @Override
    869     public boolean isReplyPathPresent() {
    870         return mReplyPathPresent;
    871     }
    872 
    873     /**
    874      * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
    875      * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
    876      * ME/TA converts each octet of TP data unit into two IRA character long
    877      * hex number (e.g. octet with integer value 42 is presented to TE as two
    878      * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
    879      * something else...
    880      */
    881     private void parsePdu(byte[] pdu) {
    882         mPdu = pdu;
    883         // Rlog.d(LOG_TAG, "raw sms message:");
    884         // Rlog.d(LOG_TAG, s);
    885 
    886         PduParser p = new PduParser(pdu);
    887 
    888         mScAddress = p.getSCAddress();
    889 
    890         if (mScAddress != null) {
    891             if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
    892         }
    893 
    894         // TODO(mkf) support reply path, user data header indicator
    895 
    896         // TP-Message-Type-Indicator
    897         // 9.2.3
    898         int firstByte = p.getByte();
    899 
    900         mMti = firstByte & 0x3;
    901         switch (mMti) {
    902         // TP-Message-Type-Indicator
    903         // 9.2.3
    904         case 0:
    905         case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
    906                 //This should be processed in the same way as MTI == 0 (Deliver)
    907             parseSmsDeliver(p, firstByte);
    908             break;
    909         case 1:
    910             parseSmsSubmit(p, firstByte);
    911             break;
    912         case 2:
    913             parseSmsStatusReport(p, firstByte);
    914             break;
    915         default:
    916             // TODO(mkf) the rest of these
    917             throw new RuntimeException("Unsupported message type");
    918         }
    919     }
    920 
    921     /**
    922      * Parses a SMS-STATUS-REPORT message.
    923      *
    924      * @param p A PduParser, cued past the first byte.
    925      * @param firstByte The first byte of the PDU, which contains MTI, etc.
    926      */
    927     private void parseSmsStatusReport(PduParser p, int firstByte) {
    928         mIsStatusReportMessage = true;
    929 
    930         // TP-Message-Reference
    931         mMessageRef = p.getByte();
    932         // TP-Recipient-Address
    933         mRecipientAddress = p.getAddress();
    934         // TP-Service-Centre-Time-Stamp
    935         mScTimeMillis = p.getSCTimestampMillis();
    936         p.getSCTimestampMillis();
    937         // TP-Status
    938         mStatus = p.getByte();
    939 
    940         // The following are optional fields that may or may not be present.
    941         if (p.moreDataPresent()) {
    942             // TP-Parameter-Indicator
    943             int extraParams = p.getByte();
    944             int moreExtraParams = extraParams;
    945             while ((moreExtraParams & 0x80) != 0) {
    946                 // We only know how to parse a few extra parameters, all
    947                 // indicated in the first TP-PI octet, so skip over any
    948                 // additional TP-PI octets.
    949                 moreExtraParams = p.getByte();
    950             }
    951             // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
    952             // only process the byte if the reserved bits (bits3 to 6) are zero.
    953             if ((extraParams & 0x78) == 0) {
    954                 // TP-Protocol-Identifier
    955                 if ((extraParams & 0x01) != 0) {
    956                     mProtocolIdentifier = p.getByte();
    957                 }
    958                 // TP-Data-Coding-Scheme
    959                 if ((extraParams & 0x02) != 0) {
    960                     mDataCodingScheme = p.getByte();
    961                 }
    962                 // TP-User-Data-Length (implies existence of TP-User-Data)
    963                 if ((extraParams & 0x04) != 0) {
    964                     boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
    965                     parseUserData(p, hasUserDataHeader);
    966                 }
    967             }
    968         }
    969     }
    970 
    971     private void parseSmsDeliver(PduParser p, int firstByte) {
    972         mReplyPathPresent = (firstByte & 0x80) == 0x80;
    973 
    974         mOriginatingAddress = p.getAddress();
    975 
    976         if (mOriginatingAddress != null) {
    977             if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
    978                     + mOriginatingAddress.address);
    979         }
    980 
    981         // TP-Protocol-Identifier (TP-PID)
    982         // TS 23.040 9.2.3.9
    983         mProtocolIdentifier = p.getByte();
    984 
    985         // TP-Data-Coding-Scheme
    986         // see TS 23.038
    987         mDataCodingScheme = p.getByte();
    988 
    989         if (VDBG) {
    990             Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
    991                     + " data coding scheme: " + mDataCodingScheme);
    992         }
    993 
    994         mScTimeMillis = p.getSCTimestampMillis();
    995 
    996         if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
    997 
    998         boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
    999 
   1000         parseUserData(p, hasUserDataHeader);
   1001     }
   1002 
   1003     /**
   1004      * Parses a SMS-SUBMIT message.
   1005      *
   1006      * @param p A PduParser, cued past the first byte.
   1007      * @param firstByte The first byte of the PDU, which contains MTI, etc.
   1008      */
   1009     private void parseSmsSubmit(PduParser p, int firstByte) {
   1010         mReplyPathPresent = (firstByte & 0x80) == 0x80;
   1011 
   1012         // TP-MR (TP-Message Reference)
   1013         mMessageRef = p.getByte();
   1014 
   1015         mRecipientAddress = p.getAddress();
   1016 
   1017         if (mRecipientAddress != null) {
   1018             if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
   1019         }
   1020 
   1021         // TP-Protocol-Identifier (TP-PID)
   1022         // TS 23.040 9.2.3.9
   1023         mProtocolIdentifier = p.getByte();
   1024 
   1025         // TP-Data-Coding-Scheme
   1026         // see TS 23.038
   1027         mDataCodingScheme = p.getByte();
   1028 
   1029         if (VDBG) {
   1030             Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
   1031                     + " data coding scheme: " + mDataCodingScheme);
   1032         }
   1033 
   1034         // TP-Validity-Period-Format
   1035         int validityPeriodLength = 0;
   1036         int validityPeriodFormat = ((firstByte>>3) & 0x3);
   1037         if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
   1038         {
   1039             validityPeriodLength = 0;
   1040         }
   1041         else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
   1042         {
   1043             validityPeriodLength = 1;
   1044         }
   1045         else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
   1046         {
   1047             validityPeriodLength = 7;
   1048         }
   1049 
   1050         // TP-Validity-Period is not used on phone, so just ignore it for now.
   1051         while (validityPeriodLength-- > 0)
   1052         {
   1053             p.getByte();
   1054         }
   1055 
   1056         boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
   1057 
   1058         parseUserData(p, hasUserDataHeader);
   1059     }
   1060 
   1061     /**
   1062      * Parses the User Data of an SMS.
   1063      *
   1064      * @param p The current PduParser.
   1065      * @param hasUserDataHeader Indicates whether a header is present in the
   1066      *                          User Data.
   1067      */
   1068     private void parseUserData(PduParser p, boolean hasUserDataHeader) {
   1069         boolean hasMessageClass = false;
   1070         boolean userDataCompressed = false;
   1071 
   1072         int encodingType = ENCODING_UNKNOWN;
   1073 
   1074         // Look up the data encoding scheme
   1075         if ((mDataCodingScheme & 0x80) == 0) {
   1076             userDataCompressed = (0 != (mDataCodingScheme & 0x20));
   1077             hasMessageClass = (0 != (mDataCodingScheme & 0x10));
   1078 
   1079             if (userDataCompressed) {
   1080                 Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
   1081                         + "(compression) " + (mDataCodingScheme & 0xff));
   1082             } else {
   1083                 switch ((mDataCodingScheme >> 2) & 0x3) {
   1084                 case 0: // GSM 7 bit default alphabet
   1085                     encodingType = ENCODING_7BIT;
   1086                     break;
   1087 
   1088                 case 2: // UCS 2 (16bit)
   1089                     encodingType = ENCODING_16BIT;
   1090                     break;
   1091 
   1092                 case 1: // 8 bit data
   1093                 case 3: // reserved
   1094                     Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
   1095                             + (mDataCodingScheme & 0xff));
   1096                     encodingType = ENCODING_8BIT;
   1097                     break;
   1098                 }
   1099             }
   1100         } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
   1101             hasMessageClass = true;
   1102             userDataCompressed = false;
   1103 
   1104             if (0 == (mDataCodingScheme & 0x04)) {
   1105                 // GSM 7 bit default alphabet
   1106                 encodingType = ENCODING_7BIT;
   1107             } else {
   1108                 // 8 bit data
   1109                 encodingType = ENCODING_8BIT;
   1110             }
   1111         } else if ((mDataCodingScheme & 0xF0) == 0xC0
   1112                 || (mDataCodingScheme & 0xF0) == 0xD0
   1113                 || (mDataCodingScheme & 0xF0) == 0xE0) {
   1114             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
   1115 
   1116             // 0xC0 == 7 bit, don't store
   1117             // 0xD0 == 7 bit, store
   1118             // 0xE0 == UCS-2, store
   1119 
   1120             if ((mDataCodingScheme & 0xF0) == 0xE0) {
   1121                 encodingType = ENCODING_16BIT;
   1122             } else {
   1123                 encodingType = ENCODING_7BIT;
   1124             }
   1125 
   1126             userDataCompressed = false;
   1127             boolean active = ((mDataCodingScheme & 0x08) == 0x08);
   1128 
   1129             // bit 0x04 reserved
   1130 
   1131             if ((mDataCodingScheme & 0x03) == 0x00) {
   1132                 mIsMwi = true;
   1133                 mMwiSense = active;
   1134                 mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
   1135             } else {
   1136                 mIsMwi = false;
   1137 
   1138                 Rlog.w(LOG_TAG, "MWI for fax, email, or other "
   1139                         + (mDataCodingScheme & 0xff));
   1140             }
   1141         } else if ((mDataCodingScheme & 0xC0) == 0x80) {
   1142             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
   1143             // 0x80..0xBF == Reserved coding groups
   1144             if (mDataCodingScheme == 0x84) {
   1145                 // This value used for KSC5601 by carriers in Korea.
   1146                 encodingType = ENCODING_KSC5601;
   1147             } else {
   1148                 Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
   1149                         + (mDataCodingScheme & 0xff));
   1150             }
   1151         } else {
   1152             Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
   1153                     + (mDataCodingScheme & 0xff));
   1154         }
   1155 
   1156         // set both the user data and the user data header.
   1157         int count = p.constructUserData(hasUserDataHeader,
   1158                 encodingType == ENCODING_7BIT);
   1159         this.mUserData = p.getUserData();
   1160         this.mUserDataHeader = p.getUserDataHeader();
   1161 
   1162         switch (encodingType) {
   1163         case ENCODING_UNKNOWN:
   1164         case ENCODING_8BIT:
   1165             mMessageBody = null;
   1166             break;
   1167 
   1168         case ENCODING_7BIT:
   1169             mMessageBody = p.getUserDataGSM7Bit(count,
   1170                     hasUserDataHeader ? mUserDataHeader.languageTable : 0,
   1171                     hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
   1172             break;
   1173 
   1174         case ENCODING_16BIT:
   1175             mMessageBody = p.getUserDataUCS2(count);
   1176             break;
   1177 
   1178         case ENCODING_KSC5601:
   1179             mMessageBody = p.getUserDataKSC5601(count);
   1180             break;
   1181         }
   1182 
   1183         if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
   1184 
   1185         if (mMessageBody != null) {
   1186             parseMessageBody();
   1187         }
   1188 
   1189         if (!hasMessageClass) {
   1190             messageClass = MessageClass.UNKNOWN;
   1191         } else {
   1192             switch (mDataCodingScheme & 0x3) {
   1193             case 0:
   1194                 messageClass = MessageClass.CLASS_0;
   1195                 break;
   1196             case 1:
   1197                 messageClass = MessageClass.CLASS_1;
   1198                 break;
   1199             case 2:
   1200                 messageClass = MessageClass.CLASS_2;
   1201                 break;
   1202             case 3:
   1203                 messageClass = MessageClass.CLASS_3;
   1204                 break;
   1205             }
   1206         }
   1207     }
   1208 
   1209     /**
   1210      * {@inheritDoc}
   1211      */
   1212     @Override
   1213     public MessageClass getMessageClass() {
   1214         return messageClass;
   1215     }
   1216 
   1217     /**
   1218      * Returns true if this is a (U)SIM data download type SM.
   1219      * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
   1220      *
   1221      * @return true if this is a USIM data download message; false otherwise
   1222      */
   1223     boolean isUsimDataDownload() {
   1224         return messageClass == MessageClass.CLASS_2 &&
   1225                 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
   1226     }
   1227 }
   1228