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