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