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