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