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