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      */
setSubId(int subId)122     public void setSubId(int subId) {
123         mSubId = subId;
124     }
125 
126     /** get Subscription information
127      *
128      * @hide
129      */
getSubId()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
toString()140         public String toString() {
141             return "SubmitPdu: encodedScAddress = "
142                     + Arrays.toString(encodedScAddress)
143                     + ", encodedMessage = "
144                     + Arrays.toString(encodedMessage);
145         }
146 
147         /**
148          * @hide
149          */
SubmitPdu(SubmitPduBase spb)150         protected SubmitPdu(SubmitPduBase spb) {
151             this.encodedMessage = spb.encodedMessage;
152             this.encodedScAddress = spb.encodedScAddress;
153         }
154 
155     }
156 
157     /**
158      * @hide
159      */
SmsMessage(SmsMessageBase smb)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
createFromPdu(byte[] pdu)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      */
createFromPdu(byte[] pdu, String format)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      */
newFromCMT(byte[] pdu)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      */
createFromEfRecord(int index, byte[] data)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      */
createFromEfRecord(int index, byte[] data, int subId)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      */
getTPLayerLengthForPDU(String pdu)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      */
calculateLength(CharSequence msgBody, boolean use7bitOnly)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      */
fragmentText(String text)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      */
calculateLength(String messageBody, boolean use7bitOnly)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      */
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested)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      */
getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, int subId)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      */
getSubmitPdu(String scAddress, String destinationAddress, short destinationPort, byte[] data, boolean statusReportRequested)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      */
getServiceCenterAddress()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
getOriginatingAddress()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      */
getDisplayOriginatingAddress()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      */
getMessageBody()602     public String getMessageBody() {
603         return mWrappedSmsMessage.getMessageBody();
604     }
605 
606     /**
607      * Returns the class of this message.
608      */
getMessageClass()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      */
getDisplayMessageBody()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      */
getPseudoSubject()632     public String getPseudoSubject() {
633         return mWrappedSmsMessage.getPseudoSubject();
634     }
635 
636     /**
637      * Returns the service centre timestamp in currentTimeMillis() format
638      */
getTimestampMillis()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      */
isEmail()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      */
getEmailBody()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      */
getEmailFrom()665     public String getEmailFrom() {
666         return mWrappedSmsMessage.getEmailFrom();
667     }
668 
669     /**
670      * Get protocol identifier.
671      */
getProtocolIdentifier()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      */
isReplace()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      */
isCphsMwiMessage()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      */
isMWIClearMessage()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      */
isMWISetMessage()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      */
isMwiDontStore()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      */
getUserData()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      */
getPdu()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      */
getStatusOnSim()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      */
getStatusOnIcc()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      */
getIndexOnSim()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      */
getIndexOnIcc()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      */
getStatus()799     public int getStatus() {
800         return mWrappedSmsMessage.getStatus();
801     }
802 
803     /**
804      * Return true iff the message is a SMS-STATUS-REPORT message.
805      */
isStatusReportMessage()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      */
isReplyPathPresent()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      */
useCdmaFormatForMoSms()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      */
useCdmaFormatForMoSms(int subId)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      */
isCdmaVoice()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       */
isCdmaVoice(int subId)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      */
hasEmsSupport()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      */
shouldAppendPageNumberAsPrefix()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 
NoEmsSupportConfig(String[] config)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
toString()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 
isNoEmsSupportConfigListExisted()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