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