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