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