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