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