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