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