Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2008 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 android.telephony;
     18 
     19 import android.os.Binder;
     20 import android.os.Parcel;
     21 import android.content.res.Resources;
     22 import android.text.TextUtils;
     23 
     24 import com.android.internal.telephony.GsmAlphabet;
     25 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
     26 import com.android.internal.telephony.SmsConstants;
     27 import com.android.internal.telephony.SmsMessageBase;
     28 import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
     29 import com.android.internal.telephony.Sms7BitEncodingTranslator;
     30 
     31 import java.lang.Math;
     32 import java.util.ArrayList;
     33 import java.util.Arrays;
     34 
     35 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
     36 
     37 
     38 /**
     39  * A Short Message Service message.
     40  * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
     41  */
     42 public class SmsMessage {
     43     private static final String LOG_TAG = "SmsMessage";
     44 
     45     /**
     46      * SMS Class enumeration.
     47      * See TS 23.038.
     48      *
     49      */
     50     public enum MessageClass{
     51         UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
     52     }
     53 
     54     /** User data text encoding code unit size */
     55     public static final int ENCODING_UNKNOWN = 0;
     56     public static final int ENCODING_7BIT = 1;
     57     public static final int ENCODING_8BIT = 2;
     58     public static final int ENCODING_16BIT = 3;
     59     /**
     60      * @hide This value is not defined in global standard. Only in Korea, this is used.
     61      */
     62     public static final int ENCODING_KSC5601 = 4;
     63 
     64     /** The maximum number of payload bytes per message */
     65     public static final int MAX_USER_DATA_BYTES = 140;
     66 
     67     /**
     68      * The maximum number of payload bytes per message if a user data header
     69      * is present.  This assumes the header only contains the
     70      * CONCATENATED_8_BIT_REFERENCE element.
     71      */
     72     public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
     73 
     74     /** The maximum number of payload septets per message */
     75     public static final int MAX_USER_DATA_SEPTETS = 160;
     76 
     77     /**
     78      * The maximum number of payload septets per message if a user data header
     79      * is present.  This assumes the header only contains the
     80      * CONCATENATED_8_BIT_REFERENCE element.
     81      */
     82     public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
     83 
     84     /**
     85      * Indicates a 3GPP format SMS message.
     86      * @hide pending API council approval
     87      */
     88     public static final String FORMAT_3GPP = "3gpp";
     89 
     90     /**
     91      * Indicates a 3GPP2 format SMS message.
     92      * @hide pending API council approval
     93      */
     94     public static final String FORMAT_3GPP2 = "3gpp2";
     95 
     96     /** Contains actual SmsMessage. Only public for debugging and for framework layer.
     97      *
     98      * @hide
     99      */
    100     public SmsMessageBase mWrappedSmsMessage;
    101 
    102     /** Indicates the subId
    103      *
    104      * @hide
    105      */
    106     private int mSubId = 0;
    107 
    108     /** set Subscription information
    109      *
    110      * @hide
    111      */
    112     public void setSubId(int subId) {
    113         mSubId = subId;
    114     }
    115 
    116     /** get Subscription information
    117      *
    118      * @hide
    119      */
    120     public int getSubId() {
    121         return mSubId;
    122     }
    123 
    124     public static class SubmitPdu {
    125 
    126         public byte[] encodedScAddress; // Null if not applicable.
    127         public byte[] encodedMessage;
    128 
    129         @Override
    130         public String toString() {
    131             return "SubmitPdu: encodedScAddress = "
    132                     + Arrays.toString(encodedScAddress)
    133                     + ", encodedMessage = "
    134                     + Arrays.toString(encodedMessage);
    135         }
    136 
    137         /**
    138          * @hide
    139          */
    140         protected SubmitPdu(SubmitPduBase spb) {
    141             this.encodedMessage = spb.encodedMessage;
    142             this.encodedScAddress = spb.encodedScAddress;
    143         }
    144 
    145     }
    146 
    147     /**
    148      * @hide
    149      */
    150     public SmsMessage(SmsMessageBase smb) {
    151         mWrappedSmsMessage = smb;
    152     }
    153 
    154     /**
    155      * Create an SmsMessage from a raw PDU. Guess format based on Voice
    156      * technology first, if it fails use other format.
    157      * All applications which handle
    158      * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
    159      * intent <b>must</b> now pass the new {@code format} String extra from the intent
    160      * into the new method {@code createFromPdu(byte[], String)} which takes an
    161      * extra format parameter. This is required in order to correctly decode the PDU on
    162      * devices that require support for both 3GPP and 3GPP2 formats at the same time,
    163      * such as dual-mode GSM/CDMA and CDMA/LTE phones.
    164      * @deprecated Use {@link #createFromPdu(byte[], String)} instead.
    165      */
    166     @Deprecated
    167     public static SmsMessage createFromPdu(byte[] pdu) {
    168          SmsMessage message = null;
    169 
    170         // cdma(3gpp2) vs gsm(3gpp) format info was not given,
    171         // guess from active voice phone type
    172         int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
    173         String format = (PHONE_TYPE_CDMA == activePhone) ?
    174                 SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
    175         message = createFromPdu(pdu, format);
    176 
    177         if (null == message || null == message.mWrappedSmsMessage) {
    178             // decoding pdu failed based on activePhone type, must be other format
    179             format = (PHONE_TYPE_CDMA == activePhone) ?
    180                     SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2;
    181             message = createFromPdu(pdu, format);
    182         }
    183         return message;
    184     }
    185 
    186     /**
    187      * Create an SmsMessage from a raw PDU with the specified message format. The
    188      * message format is passed in the
    189      * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format}
    190      * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
    191      * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
    192      *
    193      * @param pdu the message PDU from the
    194      * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
    195      * @param format the format extra from the
    196      * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
    197      */
    198     public static SmsMessage createFromPdu(byte[] pdu, String format) {
    199         SmsMessageBase wrappedMessage;
    200 
    201         if (SmsConstants.FORMAT_3GPP2.equals(format)) {
    202             wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
    203         } else if (SmsConstants.FORMAT_3GPP.equals(format)) {
    204             wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
    205         } else {
    206             Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
    207             return null;
    208         }
    209 
    210         if (wrappedMessage != null) {
    211             return new SmsMessage(wrappedMessage);
    212         } else {
    213             Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null");
    214             return null;
    215         }
    216     }
    217 
    218     /**
    219      * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
    220      * +CMT unsolicited response (PDU mode, of course)
    221      *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
    222      *
    223      * Only public for debugging and for RIL
    224      *
    225      * {@hide}
    226      */
    227     public static SmsMessage newFromCMT(byte[] pdu) {
    228         // received SMS in 3GPP format
    229         SmsMessageBase wrappedMessage =
    230                 com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu);
    231 
    232         if (wrappedMessage != null) {
    233             return new SmsMessage(wrappedMessage);
    234         } else {
    235             Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null");
    236             return null;
    237         }
    238     }
    239 
    240     /**
    241      * Create an SmsMessage from an SMS EF record.
    242      *
    243      * @param index Index of SMS record. This should be index in ArrayList
    244      *              returned by SmsManager.getAllMessagesFromSim + 1.
    245      * @param data Record data.
    246      * @return An SmsMessage representing the record.
    247      *
    248      * @hide
    249      */
    250     public static SmsMessage createFromEfRecord(int index, byte[] data) {
    251         SmsMessageBase wrappedMessage;
    252 
    253         if (isCdmaVoice()) {
    254             wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
    255                     index, data);
    256         } else {
    257             wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
    258                     index, data);
    259         }
    260 
    261         if (wrappedMessage != null) {
    262             return new SmsMessage(wrappedMessage);
    263         } else {
    264             Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null");
    265             return null;
    266         }
    267     }
    268 
    269     /**
    270      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
    271      * length in bytes (not hex chars) less the SMSC header
    272      *
    273      * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
    274      * We should probably deprecate it and remove the obsolete test case.
    275      */
    276     public static int getTPLayerLengthForPDU(String pdu) {
    277         if (isCdmaVoice()) {
    278             return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
    279         } else {
    280             return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
    281         }
    282     }
    283 
    284     /*
    285      * TODO(cleanup): It would make some sense if the result of
    286      * preprocessing a message to determine the proper encoding (i.e.
    287      * the resulting data structure from calculateLength) could be
    288      * passed as an argument to the actual final encoding function.
    289      * This would better ensure that the logic behind size calculation
    290      * actually matched the encoding.
    291      */
    292 
    293     /**
    294      * Calculates the number of SMS's required to encode the message body and
    295      * the number of characters remaining until the next message.
    296      *
    297      * @param msgBody the message to encode
    298      * @param use7bitOnly if true, characters that are not part of the
    299      *         radio-specific 7-bit encoding are counted as single
    300      *         space chars.  If false, and if the messageBody contains
    301      *         non-7-bit encodable characters, length is calculated
    302      *         using a 16-bit encoding.
    303      * @return an int[4] with int[0] being the number of SMS's
    304      *         required, int[1] the number of code units used, and
    305      *         int[2] is the number of code units remaining until the
    306      *         next message. int[3] is an indicator of the encoding
    307      *         code unit size (see the ENCODING_* definitions in SmsConstants)
    308      */
    309     public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
    310         // this function is for MO SMS
    311         TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
    312             com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly,
    313                     true) :
    314             com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
    315         int ret[] = new int[4];
    316         ret[0] = ted.msgCount;
    317         ret[1] = ted.codeUnitCount;
    318         ret[2] = ted.codeUnitsRemaining;
    319         ret[3] = ted.codeUnitSize;
    320         return ret;
    321     }
    322 
    323     /**
    324      * Divide a message text into several fragments, none bigger than
    325      * the maximum SMS message text size.
    326      *
    327      * @param text text, must not be null.
    328      * @return an <code>ArrayList</code> of strings that, in order,
    329      *   comprise the original msg text
    330      *
    331      * @hide
    332      */
    333     public static ArrayList<String> fragmentText(String text) {
    334         // This function is for MO SMS
    335         TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
    336             com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false, true) :
    337             com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);
    338 
    339         // TODO(cleanup): The code here could be rolled into the logic
    340         // below cleanly if these MAX_* constants were defined more
    341         // flexibly...
    342 
    343         int limit;
    344         if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
    345             int udhLength;
    346             if (ted.languageTable != 0 && ted.languageShiftTable != 0) {
    347                 udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES;
    348             } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) {
    349                 udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE;
    350             } else {
    351                 udhLength = 0;
    352             }
    353 
    354             if (ted.msgCount > 1) {
    355                 udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE;
    356             }
    357 
    358             if (udhLength != 0) {
    359                 udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH;
    360             }
    361 
    362             limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
    363         } else {
    364             if (ted.msgCount > 1) {
    365                 limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
    366                 // If EMS is not supported, break down EMS into single segment SMS
    367                 // and add page info " x/y".
    368                 // In the case of UCS2 encoding, we need 8 bytes for this,
    369                 // but we only have 6 bytes from UDH, so truncate the limit for
    370                 // each segment by 2 bytes (1 char).
    371                 // Make sure total number of segments is less than 10.
    372                 if (!hasEmsSupport() && ted.msgCount < 10) {
    373                     limit -= 2;
    374                 }
    375             } else {
    376                 limit = SmsConstants.MAX_USER_DATA_BYTES;
    377             }
    378         }
    379 
    380         String newMsgBody = null;
    381         Resources r = Resources.getSystem();
    382         if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
    383             newMsgBody  = Sms7BitEncodingTranslator.translate(text);
    384         }
    385         if (TextUtils.isEmpty(newMsgBody)) {
    386             newMsgBody = text;
    387         }
    388         int pos = 0;  // Index in code units.
    389         int textLen = newMsgBody.length();
    390         ArrayList<String> result = new ArrayList<String>(ted.msgCount);
    391         while (pos < textLen) {
    392             int nextPos = 0;  // Counts code units.
    393             if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
    394                 if (useCdmaFormatForMoSms() && ted.msgCount == 1) {
    395                     // For a singleton CDMA message, the encoding must be ASCII...
    396                     nextPos = pos + Math.min(limit, textLen - pos);
    397                 } else {
    398                     // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
    399                     nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit,
    400                             ted.languageTable, ted.languageShiftTable);
    401                 }
    402             } else {  // Assume unicode.
    403                 nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody);
    404             }
    405             if ((nextPos <= pos) || (nextPos > textLen)) {
    406                 Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
    407                           nextPos + " >= " + textLen + ")");
    408                 break;
    409             }
    410             result.add(newMsgBody.substring(pos, nextPos));
    411             pos = nextPos;
    412         }
    413         return result;
    414     }
    415 
    416     /**
    417      * Calculates the number of SMS's required to encode the message body and
    418      * the number of characters remaining until the next message, given the
    419      * current encoding.
    420      *
    421      * @param messageBody the message to encode
    422      * @param use7bitOnly if true, characters that are not part of the radio
    423      *         specific (GSM / CDMA) alphabet encoding are converted to as a
    424      *         single space characters. If false, a messageBody containing
    425      *         non-GSM or non-CDMA alphabet characters are encoded using
    426      *         16-bit encoding.
    427      * @return an int[4] with int[0] being the number of SMS's required, int[1]
    428      *         the number of code units used, and int[2] is the number of code
    429      *         units remaining until the next message. int[3] is the encoding
    430      *         type that should be used for the message.
    431      */
    432     public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
    433         return calculateLength((CharSequence)messageBody, use7bitOnly);
    434     }
    435 
    436     /*
    437      * TODO(cleanup): It looks like there is now no useful reason why
    438      * apps should generate pdus themselves using these routines,
    439      * instead of handing the raw data to SMSDispatcher (and thereby
    440      * have the phone process do the encoding).  Moreover, CDMA now
    441      * has shared state (in the form of the msgId system property)
    442      * which can only be modified by the phone process, and hence
    443      * makes the output of these routines incorrect.  Since they now
    444      * serve no purpose, they should probably just return null
    445      * directly, and be deprecated.  Going further in that direction,
    446      * the above parsers of serialized pdu data should probably also
    447      * be gotten rid of, hiding all but the necessarily visible
    448      * structured data from client apps.  A possible concern with
    449      * doing this is that apps may be using these routines to generate
    450      * pdus that are then sent elsewhere, some network server, for
    451      * example, and that always returning null would thereby break
    452      * otherwise useful apps.
    453      */
    454 
    455     /**
    456      * Get an SMS-SUBMIT PDU for a destination address and a message.
    457      * This method will not attempt to use any GSM national language 7 bit encodings.
    458      *
    459      * @param scAddress Service Centre address.  Null means use default.
    460      * @return a <code>SubmitPdu</code> containing the encoded SC
    461      *         address, if applicable, and the encoded message.
    462      *         Returns null on encode error.
    463      */
    464     public static SubmitPdu getSubmitPdu(String scAddress,
    465             String destinationAddress, String message, boolean statusReportRequested) {
    466         SubmitPduBase spb;
    467 
    468         if (useCdmaFormatForMoSms()) {
    469             spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
    470                     destinationAddress, message, statusReportRequested, null);
    471         } else {
    472             spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
    473                     destinationAddress, message, statusReportRequested);
    474         }
    475 
    476         return new SubmitPdu(spb);
    477     }
    478 
    479     /**
    480      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port.
    481      * This method will not attempt to use any GSM national language 7 bit encodings.
    482      *
    483      * @param scAddress Service Centre address. null == use default
    484      * @param destinationAddress the address of the destination for the message
    485      * @param destinationPort the port to deliver the message to at the
    486      *        destination
    487      * @param data the data for the message
    488      * @return a <code>SubmitPdu</code> containing the encoded SC
    489      *         address, if applicable, and the encoded message.
    490      *         Returns null on encode error.
    491      */
    492     public static SubmitPdu getSubmitPdu(String scAddress,
    493             String destinationAddress, short destinationPort, byte[] data,
    494             boolean statusReportRequested) {
    495         SubmitPduBase spb;
    496 
    497         if (useCdmaFormatForMoSms()) {
    498             spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
    499                     destinationAddress, destinationPort, data, statusReportRequested);
    500         } else {
    501             spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
    502                     destinationAddress, destinationPort, data, statusReportRequested);
    503         }
    504 
    505         return new SubmitPdu(spb);
    506     }
    507 
    508     /**
    509      * Returns the address of the SMS service center that relayed this message
    510      * or null if there is none.
    511      */
    512     public String getServiceCenterAddress() {
    513         return mWrappedSmsMessage.getServiceCenterAddress();
    514     }
    515 
    516     /**
    517      * Returns the originating address (sender) of this SMS message in String
    518      * form or null if unavailable
    519      */
    520     public String getOriginatingAddress() {
    521         return mWrappedSmsMessage.getOriginatingAddress();
    522     }
    523 
    524     /**
    525      * Returns the originating address, or email from address if this message
    526      * was from an email gateway. Returns null if originating address
    527      * unavailable.
    528      */
    529     public String getDisplayOriginatingAddress() {
    530         return mWrappedSmsMessage.getDisplayOriginatingAddress();
    531     }
    532 
    533     /**
    534      * Returns the message body as a String, if it exists and is text based.
    535      * @return message body is there is one, otherwise null
    536      */
    537     public String getMessageBody() {
    538         return mWrappedSmsMessage.getMessageBody();
    539     }
    540 
    541     /**
    542      * Returns the class of this message.
    543      */
    544     public MessageClass getMessageClass() {
    545         switch(mWrappedSmsMessage.getMessageClass()) {
    546             case CLASS_0: return MessageClass.CLASS_0;
    547             case CLASS_1: return MessageClass.CLASS_1;
    548             case CLASS_2: return MessageClass.CLASS_2;
    549             case CLASS_3: return MessageClass.CLASS_3;
    550             default: return MessageClass.UNKNOWN;
    551 
    552         }
    553     }
    554 
    555     /**
    556      * Returns the message body, or email message body if this message was from
    557      * an email gateway. Returns null if message body unavailable.
    558      */
    559     public String getDisplayMessageBody() {
    560         return mWrappedSmsMessage.getDisplayMessageBody();
    561     }
    562 
    563     /**
    564      * Unofficial convention of a subject line enclosed in parens empty string
    565      * if not present
    566      */
    567     public String getPseudoSubject() {
    568         return mWrappedSmsMessage.getPseudoSubject();
    569     }
    570 
    571     /**
    572      * Returns the service centre timestamp in currentTimeMillis() format
    573      */
    574     public long getTimestampMillis() {
    575         return mWrappedSmsMessage.getTimestampMillis();
    576     }
    577 
    578     /**
    579      * Returns true if message is an email.
    580      *
    581      * @return true if this message came through an email gateway and email
    582      *         sender / subject / parsed body are available
    583      */
    584     public boolean isEmail() {
    585         return mWrappedSmsMessage.isEmail();
    586     }
    587 
    588      /**
    589      * @return if isEmail() is true, body of the email sent through the gateway.
    590      *         null otherwise
    591      */
    592     public String getEmailBody() {
    593         return mWrappedSmsMessage.getEmailBody();
    594     }
    595 
    596     /**
    597      * @return if isEmail() is true, email from address of email sent through
    598      *         the gateway. null otherwise
    599      */
    600     public String getEmailFrom() {
    601         return mWrappedSmsMessage.getEmailFrom();
    602     }
    603 
    604     /**
    605      * Get protocol identifier.
    606      */
    607     public int getProtocolIdentifier() {
    608         return mWrappedSmsMessage.getProtocolIdentifier();
    609     }
    610 
    611     /**
    612      * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
    613      * SMS
    614      */
    615     public boolean isReplace() {
    616         return mWrappedSmsMessage.isReplace();
    617     }
    618 
    619     /**
    620      * Returns true for CPHS MWI toggle message.
    621      *
    622      * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
    623      *         B.4.2
    624      */
    625     public boolean isCphsMwiMessage() {
    626         return mWrappedSmsMessage.isCphsMwiMessage();
    627     }
    628 
    629     /**
    630      * returns true if this message is a CPHS voicemail / message waiting
    631      * indicator (MWI) clear message
    632      */
    633     public boolean isMWIClearMessage() {
    634         return mWrappedSmsMessage.isMWIClearMessage();
    635     }
    636 
    637     /**
    638      * returns true if this message is a CPHS voicemail / message waiting
    639      * indicator (MWI) set message
    640      */
    641     public boolean isMWISetMessage() {
    642         return mWrappedSmsMessage.isMWISetMessage();
    643     }
    644 
    645     /**
    646      * returns true if this message is a "Message Waiting Indication Group:
    647      * Discard Message" notification and should not be stored.
    648      */
    649     public boolean isMwiDontStore() {
    650         return mWrappedSmsMessage.isMwiDontStore();
    651     }
    652 
    653     /**
    654      * returns the user data section minus the user data header if one was
    655      * present.
    656      */
    657     public byte[] getUserData() {
    658         return mWrappedSmsMessage.getUserData();
    659     }
    660 
    661     /**
    662      * Returns the raw PDU for the message.
    663      *
    664      * @return the raw PDU for the message.
    665      */
    666     public byte[] getPdu() {
    667         return mWrappedSmsMessage.getPdu();
    668     }
    669 
    670     /**
    671      * Returns the status of the message on the SIM (read, unread, sent, unsent).
    672      *
    673      * @return the status of the message on the SIM.  These are:
    674      *         SmsManager.STATUS_ON_SIM_FREE
    675      *         SmsManager.STATUS_ON_SIM_READ
    676      *         SmsManager.STATUS_ON_SIM_UNREAD
    677      *         SmsManager.STATUS_ON_SIM_SEND
    678      *         SmsManager.STATUS_ON_SIM_UNSENT
    679      * @deprecated Use getStatusOnIcc instead.
    680      */
    681     @Deprecated public int getStatusOnSim() {
    682         return mWrappedSmsMessage.getStatusOnIcc();
    683     }
    684 
    685     /**
    686      * Returns the status of the message on the ICC (read, unread, sent, unsent).
    687      *
    688      * @return the status of the message on the ICC.  These are:
    689      *         SmsManager.STATUS_ON_ICC_FREE
    690      *         SmsManager.STATUS_ON_ICC_READ
    691      *         SmsManager.STATUS_ON_ICC_UNREAD
    692      *         SmsManager.STATUS_ON_ICC_SEND
    693      *         SmsManager.STATUS_ON_ICC_UNSENT
    694      */
    695     public int getStatusOnIcc() {
    696         return mWrappedSmsMessage.getStatusOnIcc();
    697     }
    698 
    699     /**
    700      * Returns the record index of the message on the SIM (1-based index).
    701      * @return the record index of the message on the SIM, or -1 if this
    702      *         SmsMessage was not created from a SIM SMS EF record.
    703      * @deprecated Use getIndexOnIcc instead.
    704      */
    705     @Deprecated public int getIndexOnSim() {
    706         return mWrappedSmsMessage.getIndexOnIcc();
    707     }
    708 
    709     /**
    710      * Returns the record index of the message on the ICC (1-based index).
    711      * @return the record index of the message on the ICC, or -1 if this
    712      *         SmsMessage was not created from a ICC SMS EF record.
    713      */
    714     public int getIndexOnIcc() {
    715         return mWrappedSmsMessage.getIndexOnIcc();
    716     }
    717 
    718     /**
    719      * GSM:
    720      * For an SMS-STATUS-REPORT message, this returns the status field from
    721      * the status report.  This field indicates the status of a previously
    722      * submitted SMS, if requested.  See TS 23.040, 9.2.3.15 TP-Status for a
    723      * description of values.
    724      * CDMA:
    725      * For not interfering with status codes from GSM, the value is
    726      * shifted to the bits 31-16.
    727      * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
    728      * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
    729      *
    730      * @return 0 indicates the previously sent message was received.
    731      *         See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
    732      *         for a description of other possible values.
    733      */
    734     public int getStatus() {
    735         return mWrappedSmsMessage.getStatus();
    736     }
    737 
    738     /**
    739      * Return true iff the message is a SMS-STATUS-REPORT message.
    740      */
    741     public boolean isStatusReportMessage() {
    742         return mWrappedSmsMessage.isStatusReportMessage();
    743     }
    744 
    745     /**
    746      * Returns true iff the <code>TP-Reply-Path</code> bit is set in
    747      * this message.
    748      */
    749     public boolean isReplyPathPresent() {
    750         return mWrappedSmsMessage.isReplyPathPresent();
    751     }
    752 
    753     /**
    754      * Determines whether or not to use CDMA format for MO SMS.
    755      * If SMS over IMS is supported, then format is based on IMS SMS format,
    756      * otherwise format is based on current phone type.
    757      *
    758      * @return true if Cdma format should be used for MO SMS, false otherwise.
    759      */
    760     private static boolean useCdmaFormatForMoSms() {
    761         if (!SmsManager.getDefault().isImsSmsSupported()) {
    762             // use Voice technology to determine SMS format.
    763             return isCdmaVoice();
    764         }
    765         // IMS is registered with SMS support, check the SMS format supported
    766         return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
    767     }
    768 
    769     /**
    770      * Determines whether or not to current phone type is cdma.
    771      *
    772      * @return true if current phone type is cdma, false otherwise.
    773      */
    774     private static boolean isCdmaVoice() {
    775         int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
    776         return (PHONE_TYPE_CDMA == activePhone);
    777     }
    778 
    779     /**
    780      * Decide if the carrier supports long SMS.
    781      * {@hide}
    782      */
    783     public static boolean hasEmsSupport() {
    784         if (!isNoEmsSupportConfigListExisted()) {
    785             return true;
    786         }
    787 
    788         String simOperator;
    789         String gid;
    790         final long identity = Binder.clearCallingIdentity();
    791         try {
    792             simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
    793             gid = TelephonyManager.getDefault().getGroupIdLevel1();
    794         } finally {
    795             Binder.restoreCallingIdentity(identity);
    796         }
    797 
    798         if (!TextUtils.isEmpty(simOperator)) {
    799             for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
    800                 if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
    801                         (TextUtils.isEmpty(currentConfig.mGid1) ||
    802                                 (!TextUtils.isEmpty(currentConfig.mGid1) &&
    803                                         currentConfig.mGid1.equalsIgnoreCase(gid)))) {
    804                     return false;
    805                 }
    806             }
    807         }
    808         return true;
    809     }
    810 
    811     /**
    812      * Check where to add " x/y" in each SMS segment, begin or end.
    813      * {@hide}
    814      */
    815     public static boolean shouldAppendPageNumberAsPrefix() {
    816         if (!isNoEmsSupportConfigListExisted()) {
    817             return false;
    818         }
    819 
    820         String simOperator;
    821         String gid;
    822         final long identity = Binder.clearCallingIdentity();
    823         try {
    824             simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
    825             gid = TelephonyManager.getDefault().getGroupIdLevel1();
    826         } finally {
    827             Binder.restoreCallingIdentity(identity);
    828         }
    829 
    830         for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
    831             if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
    832                 (TextUtils.isEmpty(currentConfig.mGid1) ||
    833                 (!TextUtils.isEmpty(currentConfig.mGid1)
    834                 && currentConfig.mGid1.equalsIgnoreCase(gid)))) {
    835                 return currentConfig.mIsPrefix;
    836             }
    837         }
    838         return false;
    839     }
    840 
    841     private static class NoEmsSupportConfig {
    842         String mOperatorNumber;
    843         String mGid1;
    844         boolean mIsPrefix;
    845 
    846         public NoEmsSupportConfig(String[] config) {
    847             mOperatorNumber = config[0];
    848             mIsPrefix = "prefix".equals(config[1]);
    849             mGid1 = config.length > 2 ? config[2] : null;
    850         }
    851 
    852         @Override
    853         public String toString() {
    854             return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber
    855                     + ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }";
    856         }
    857     }
    858 
    859     private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null;
    860     private static boolean mIsNoEmsSupportConfigListLoaded = false;
    861 
    862     private static boolean isNoEmsSupportConfigListExisted() {
    863         if (!mIsNoEmsSupportConfigListLoaded) {
    864             Resources r = Resources.getSystem();
    865             if (r != null) {
    866                 String[] listArray = r.getStringArray(
    867                         com.android.internal.R.array.no_ems_support_sim_operators);
    868                 if ((listArray != null) && (listArray.length > 0)) {
    869                     mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
    870                     for (int i=0; i<listArray.length; i++) {
    871                         mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
    872                     }
    873                 }
    874                 mIsNoEmsSupportConfigListLoaded = true;
    875             }
    876         }
    877 
    878         if (mNoEmsSupportConfigList != null && mNoEmsSupportConfigList.length != 0) {
    879             return true;
    880         }
    881 
    882         return false;
    883     }
    884 }
    885