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