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