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