SmsMessage.java revision 6aa799c9d555854ecac94b15967e9016c55b4340
1/* 2 * Copyright (C) 2006 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 17package com.android.internal.telephony.gsm; 18 19import android.telephony.PhoneNumberUtils; 20import android.text.format.Time; 21import android.telephony.Rlog; 22import android.content.res.Resources; 23 24import com.android.internal.telephony.EncodeException; 25import com.android.internal.telephony.GsmAlphabet; 26import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 27import com.android.internal.telephony.uicc.IccUtils; 28import com.android.internal.telephony.SmsHeader; 29import com.android.internal.telephony.SmsMessageBase; 30 31import java.io.ByteArrayOutputStream; 32import java.io.UnsupportedEncodingException; 33import java.text.ParseException; 34 35import static com.android.internal.telephony.SmsConstants.MessageClass; 36import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN; 37import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT; 38import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT; 39import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT; 40import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601; 41import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS; 42import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES; 43import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER; 44 45/** 46 * A Short Message Service message. 47 * 48 */ 49public class SmsMessage extends SmsMessageBase { 50 static final String LOG_TAG = "SmsMessage"; 51 private static final boolean VDBG = false; 52 53 private MessageClass messageClass; 54 55 /** 56 * TP-Message-Type-Indicator 57 * 9.2.3 58 */ 59 private int mMti; 60 61 /** TP-Protocol-Identifier (TP-PID) */ 62 private int mProtocolIdentifier; 63 64 // TP-Data-Coding-Scheme 65 // see TS 23.038 66 private int mDataCodingScheme; 67 68 // TP-Reply-Path 69 // e.g. 23.040 9.2.2.1 70 private boolean mReplyPathPresent = false; 71 72 /** The address of the receiver. */ 73 private GsmSmsAddress mRecipientAddress; 74 75 /** 76 * TP-Status - status of a previously submitted SMS. 77 * This field applies to SMS-STATUS-REPORT messages. 0 indicates success; 78 * see TS 23.040, 9.2.3.15 for description of other possible values. 79 */ 80 private int mStatus; 81 82 /** 83 * TP-Status - status of a previously submitted SMS. 84 * This field is true iff the message is a SMS-STATUS-REPORT message. 85 */ 86 private boolean mIsStatusReportMessage = false; 87 88 private int mVoiceMailCount = 0; 89 90 public static class SubmitPdu extends SubmitPduBase { 91 } 92 93 /** 94 * Create an SmsMessage from a raw PDU. 95 */ 96 public static SmsMessage createFromPdu(byte[] pdu) { 97 try { 98 SmsMessage msg = new SmsMessage(); 99 msg.parsePdu(pdu); 100 return msg; 101 } catch (RuntimeException ex) { 102 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 103 return null; 104 } catch (OutOfMemoryError e) { 105 Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e); 106 return null; 107 } 108 } 109 110 /** 111 * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated 112 * by TP_PID field set to value 0x40 113 */ 114 public boolean isTypeZero() { 115 return (mProtocolIdentifier == 0x40); 116 } 117 118 /** 119 * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the 120 * +CMT unsolicited response (PDU mode, of course) 121 * +CMT: [<alpha>],<length><CR><LF><pdu> 122 * 123 * Only public for debugging 124 * 125 * {@hide} 126 */ 127 public static SmsMessage newFromCMT(String[] lines) { 128 try { 129 SmsMessage msg = new SmsMessage(); 130 msg.parsePdu(IccUtils.hexStringToBytes(lines[1])); 131 return msg; 132 } catch (RuntimeException ex) { 133 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 134 return null; 135 } 136 } 137 138 /** @hide */ 139 public static SmsMessage newFromCDS(String line) { 140 try { 141 SmsMessage msg = new SmsMessage(); 142 msg.parsePdu(IccUtils.hexStringToBytes(line)); 143 return msg; 144 } catch (RuntimeException ex) { 145 Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex); 146 return null; 147 } 148 } 149 150 /** 151 * Create an SmsMessage from an SMS EF record. 152 * 153 * @param index Index of SMS record. This should be index in ArrayList 154 * returned by SmsManager.getAllMessagesFromSim + 1. 155 * @param data Record data. 156 * @return An SmsMessage representing the record. 157 * 158 * @hide 159 */ 160 public static SmsMessage createFromEfRecord(int index, byte[] data) { 161 try { 162 SmsMessage msg = new SmsMessage(); 163 164 msg.mIndexOnIcc = index; 165 166 // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT, 167 // or STORED_UNSENT 168 // See TS 51.011 10.5.3 169 if ((data[0] & 1) == 0) { 170 Rlog.w(LOG_TAG, 171 "SMS parsing failed: Trying to parse a free record"); 172 return null; 173 } else { 174 msg.mStatusOnIcc = data[0] & 0x07; 175 } 176 177 int size = data.length - 1; 178 179 // Note: Data may include trailing FF's. That's OK; message 180 // should still parse correctly. 181 byte[] pdu = new byte[size]; 182 System.arraycopy(data, 1, pdu, 0, size); 183 msg.parsePdu(pdu); 184 return msg; 185 } catch (RuntimeException ex) { 186 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 187 return null; 188 } 189 } 190 191 /** 192 * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the 193 * length in bytes (not hex chars) less the SMSC header 194 */ 195 public static int getTPLayerLengthForPDU(String pdu) { 196 int len = pdu.length() / 2; 197 int smscLen = Integer.parseInt(pdu.substring(0, 2), 16); 198 199 return len - smscLen - 1; 200 } 201 202 /** 203 * Get an SMS-SUBMIT PDU for a destination address and a message 204 * 205 * @param scAddress Service Centre address. Null means use default. 206 * @return a <code>SubmitPdu</code> containing the encoded SC 207 * address, if applicable, and the encoded message. 208 * Returns null on encode error. 209 * @hide 210 */ 211 public static SubmitPdu getSubmitPdu(String scAddress, 212 String destinationAddress, String message, 213 boolean statusReportRequested, byte[] header) { 214 return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header, 215 ENCODING_UNKNOWN, 0, 0); 216 } 217 218 219 /** 220 * Get an SMS-SUBMIT PDU for a destination address and a message using the 221 * specified encoding. 222 * 223 * @param scAddress Service Centre address. Null means use default. 224 * @param encoding Encoding defined by constants in 225 * com.android.internal.telephony.SmsConstants.ENCODING_* 226 * @param languageTable 227 * @param languageShiftTable 228 * @return a <code>SubmitPdu</code> containing the encoded SC 229 * address, if applicable, and the encoded message. 230 * Returns null on encode error. 231 * @hide 232 */ 233 public static SubmitPdu getSubmitPdu(String scAddress, 234 String destinationAddress, String message, 235 boolean statusReportRequested, byte[] header, int encoding, 236 int languageTable, int languageShiftTable) { 237 238 // Perform null parameter checks. 239 if (message == null || destinationAddress == null) { 240 return null; 241 } 242 243 if (encoding == ENCODING_UNKNOWN) { 244 // Find the best encoding to use 245 TextEncodingDetails ted = calculateLength(message, false); 246 encoding = ted.codeUnitSize; 247 languageTable = ted.languageTable; 248 languageShiftTable = ted.languageShiftTable; 249 250 if (encoding == ENCODING_7BIT && 251 (languageTable != 0 || languageShiftTable != 0)) { 252 if (header != null) { 253 SmsHeader smsHeader = SmsHeader.fromByteArray(header); 254 if (smsHeader.languageTable != languageTable 255 || smsHeader.languageShiftTable != languageShiftTable) { 256 Rlog.w(LOG_TAG, "Updating language table in SMS header: " 257 + smsHeader.languageTable + " -> " + languageTable + ", " 258 + smsHeader.languageShiftTable + " -> " + languageShiftTable); 259 smsHeader.languageTable = languageTable; 260 smsHeader.languageShiftTable = languageShiftTable; 261 header = SmsHeader.toByteArray(smsHeader); 262 } 263 } else { 264 SmsHeader smsHeader = new SmsHeader(); 265 smsHeader.languageTable = languageTable; 266 smsHeader.languageShiftTable = languageShiftTable; 267 header = SmsHeader.toByteArray(smsHeader); 268 } 269 } 270 } 271 272 SubmitPdu ret = new SubmitPdu(); 273 // MTI = SMS-SUBMIT, UDHI = header != null 274 byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00)); 275 ByteArrayOutputStream bo = getSubmitPduHead( 276 scAddress, destinationAddress, mtiByte, 277 statusReportRequested, ret); 278 279 // User Data (and length) 280 byte[] userData; 281 try { 282 if (encoding == ENCODING_7BIT) { 283 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 284 languageTable, languageShiftTable); 285 } else { //assume UCS-2 286 try { 287 userData = encodeUCS2(message, header); 288 } catch(UnsupportedEncodingException uex) { 289 Rlog.e(LOG_TAG, 290 "Implausible UnsupportedEncodingException ", 291 uex); 292 return null; 293 } 294 } 295 } catch (EncodeException ex) { 296 // Encoding to the 7-bit alphabet failed. Let's see if we can 297 // send it as a UCS-2 encoded message 298 try { 299 userData = encodeUCS2(message, header); 300 encoding = ENCODING_16BIT; 301 } catch(UnsupportedEncodingException uex) { 302 Rlog.e(LOG_TAG, 303 "Implausible UnsupportedEncodingException ", 304 uex); 305 return null; 306 } 307 } 308 309 if (encoding == ENCODING_7BIT) { 310 if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { 311 // Message too long 312 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)"); 313 return null; 314 } 315 // TP-Data-Coding-Scheme 316 // Default encoding, uncompressed 317 // To test writing messages to the SIM card, change this value 0x00 318 // to 0x12, which means "bits 1 and 0 contain message class, and the 319 // class is 2". Note that this takes effect for the sender. In other 320 // words, messages sent by the phone with this change will end up on 321 // the receiver's SIM card. You can then send messages to yourself 322 // (on a phone with this change) and they'll end up on the SIM card. 323 bo.write(0x00); 324 } else { // assume UCS-2 325 if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { 326 // Message too long 327 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)"); 328 return null; 329 } 330 // TP-Data-Coding-Scheme 331 // UCS-2 encoding, uncompressed 332 bo.write(0x08); 333 } 334 335 // (no TP-Validity-Period) 336 bo.write(userData, 0, userData.length); 337 ret.encodedMessage = bo.toByteArray(); 338 return ret; 339 } 340 341 /** 342 * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary 343 * 344 * @return encoded message as UCS2 345 * @throws UnsupportedEncodingException 346 */ 347 private static byte[] encodeUCS2(String message, byte[] header) 348 throws UnsupportedEncodingException { 349 byte[] userData, textPart; 350 textPart = message.getBytes("utf-16be"); 351 352 if (header != null) { 353 // Need 1 byte for UDHL 354 userData = new byte[header.length + textPart.length + 1]; 355 356 userData[0] = (byte)header.length; 357 System.arraycopy(header, 0, userData, 1, header.length); 358 System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length); 359 } 360 else { 361 userData = textPart; 362 } 363 byte[] ret = new byte[userData.length+1]; 364 ret[0] = (byte) (userData.length & 0xff ); 365 System.arraycopy(userData, 0, ret, 1, userData.length); 366 return ret; 367 } 368 369 /** 370 * Get an SMS-SUBMIT PDU for a destination address and a message 371 * 372 * @param scAddress Service Centre address. Null means use default. 373 * @return a <code>SubmitPdu</code> containing the encoded SC 374 * address, if applicable, and the encoded message. 375 * Returns null on encode error. 376 */ 377 public static SubmitPdu getSubmitPdu(String scAddress, 378 String destinationAddress, String message, 379 boolean statusReportRequested) { 380 381 return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null); 382 } 383 384 /** 385 * Get an SMS-SUBMIT PDU for a data message to a destination address & port 386 * 387 * @param scAddress Service Centre address. null == use default 388 * @param destinationAddress the address of the destination for the message 389 * @param destinationPort the port to deliver the message to at the 390 * destination 391 * @param data the data for the message 392 * @return a <code>SubmitPdu</code> containing the encoded SC 393 * address, if applicable, and the encoded message. 394 * Returns null on encode error. 395 */ 396 public static SubmitPdu getSubmitPdu(String scAddress, 397 String destinationAddress, int destinationPort, byte[] data, 398 boolean statusReportRequested) { 399 400 SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs(); 401 portAddrs.destPort = destinationPort; 402 portAddrs.origPort = 0; 403 portAddrs.areEightBits = false; 404 405 SmsHeader smsHeader = new SmsHeader(); 406 smsHeader.portAddrs = portAddrs; 407 408 byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader); 409 410 if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) { 411 Rlog.e(LOG_TAG, "SMS data message may only contain " 412 + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes"); 413 return null; 414 } 415 416 SubmitPdu ret = new SubmitPdu(); 417 ByteArrayOutputStream bo = getSubmitPduHead( 418 scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT, 419 // TP-UDHI = true 420 statusReportRequested, ret); 421 422 // TP-Data-Coding-Scheme 423 // No class, 8 bit data 424 bo.write(0x04); 425 426 // (no TP-Validity-Period) 427 428 // Total size 429 bo.write(data.length + smsHeaderData.length + 1); 430 431 // User data header 432 bo.write(smsHeaderData.length); 433 bo.write(smsHeaderData, 0, smsHeaderData.length); 434 435 // User data 436 bo.write(data, 0, data.length); 437 438 ret.encodedMessage = bo.toByteArray(); 439 return ret; 440 } 441 442 /** 443 * Create the beginning of a SUBMIT PDU. This is the part of the 444 * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu}, 445 * one of which takes a byte array and the other of which takes a 446 * <code>String</code>. 447 * 448 * @param scAddress Service Centre address. null == use default 449 * @param destinationAddress the address of the destination for the message 450 * @param mtiByte 451 * @param ret <code>SubmitPdu</code> containing the encoded SC 452 * address, if applicable, and the encoded message 453 */ 454 private static ByteArrayOutputStream getSubmitPduHead( 455 String scAddress, String destinationAddress, byte mtiByte, 456 boolean statusReportRequested, SubmitPdu ret) { 457 ByteArrayOutputStream bo = new ByteArrayOutputStream( 458 MAX_USER_DATA_BYTES + 40); 459 460 // SMSC address with length octet, or 0 461 if (scAddress == null) { 462 ret.encodedScAddress = null; 463 } else { 464 ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength( 465 scAddress); 466 } 467 468 // TP-Message-Type-Indicator (and friends) 469 if (statusReportRequested) { 470 // Set TP-Status-Report-Request bit. 471 mtiByte |= 0x20; 472 if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested"); 473 } 474 bo.write(mtiByte); 475 476 // space for TP-Message-Reference 477 bo.write(0); 478 479 byte[] daBytes; 480 481 daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress); 482 483 // destination address length in BCD digits, ignoring TON byte and pad 484 // TODO Should be better. 485 bo.write((daBytes.length - 1) * 2 486 - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); 487 488 // destination address 489 bo.write(daBytes, 0, daBytes.length); 490 491 // TP-Protocol-Identifier 492 bo.write(0); 493 return bo; 494 } 495 496 private static class PduParser { 497 byte mPdu[]; 498 int mCur; 499 SmsHeader mUserDataHeader; 500 byte[] mUserData; 501 int mUserDataSeptetPadding; 502 503 PduParser(byte[] pdu) { 504 mPdu = pdu; 505 mCur = 0; 506 mUserDataSeptetPadding = 0; 507 } 508 509 /** 510 * Parse and return the SC address prepended to SMS messages coming via 511 * the TS 27.005 / AT interface. Returns null on invalid address 512 */ 513 String getSCAddress() { 514 int len; 515 String ret; 516 517 // length of SC Address 518 len = getByte(); 519 520 if (len == 0) { 521 // no SC address 522 ret = null; 523 } else { 524 // SC address 525 try { 526 ret = PhoneNumberUtils 527 .calledPartyBCDToString(mPdu, mCur, len); 528 } catch (RuntimeException tr) { 529 Rlog.d(LOG_TAG, "invalid SC address: ", tr); 530 ret = null; 531 } 532 } 533 534 mCur += len; 535 536 return ret; 537 } 538 539 /** 540 * returns non-sign-extended byte value 541 */ 542 int getByte() { 543 return mPdu[mCur++] & 0xff; 544 } 545 546 /** 547 * Any address except the SC address (eg, originating address) See TS 548 * 23.040 9.1.2.5 549 */ 550 GsmSmsAddress getAddress() { 551 GsmSmsAddress ret; 552 553 // "The Address-Length field is an integer representation of 554 // the number field, i.e. excludes any semi-octet containing only 555 // fill bits." 556 // The TOA field is not included as part of this 557 int addressLength = mPdu[mCur] & 0xff; 558 int lengthBytes = 2 + (addressLength + 1) / 2; 559 560 try { 561 ret = new GsmSmsAddress(mPdu, mCur, lengthBytes); 562 } catch (ParseException e) { 563 ret = null; 564 //This is caught by createFromPdu(byte[] pdu) 565 throw new RuntimeException(e.getMessage()); 566 } 567 568 mCur += lengthBytes; 569 570 return ret; 571 } 572 573 /** 574 * Parses an SC timestamp and returns a currentTimeMillis()-style 575 * timestamp 576 */ 577 578 long getSCTimestampMillis() { 579 // TP-Service-Centre-Time-Stamp 580 int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 581 int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 582 int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 583 int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 584 int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 585 int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 586 587 // For the timezone, the most significant bit of the 588 // least significant nibble is the sign byte 589 // (meaning the max range of this field is 79 quarter-hours, 590 // which is more than enough) 591 592 byte tzByte = mPdu[mCur++]; 593 594 // Mask out sign bit. 595 int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); 596 597 timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; 598 599 Time time = new Time(Time.TIMEZONE_UTC); 600 601 // It's 2006. Should I really support years < 2000? 602 time.year = year >= 90 ? year + 1900 : year + 2000; 603 time.month = month - 1; 604 time.monthDay = day; 605 time.hour = hour; 606 time.minute = minute; 607 time.second = second; 608 609 // Timezone offset is in quarter hours. 610 return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000); 611 } 612 613 /** 614 * Pulls the user data out of the PDU, and separates the payload from 615 * the header if there is one. 616 * 617 * @param hasUserDataHeader true if there is a user data header 618 * @param dataInSeptets true if the data payload is in septets instead 619 * of octets 620 * @return the number of septets or octets in the user data payload 621 */ 622 int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) { 623 int offset = mCur; 624 int userDataLength = mPdu[offset++] & 0xff; 625 int headerSeptets = 0; 626 int userDataHeaderLength = 0; 627 628 if (hasUserDataHeader) { 629 userDataHeaderLength = mPdu[offset++] & 0xff; 630 631 byte[] udh = new byte[userDataHeaderLength]; 632 System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength); 633 mUserDataHeader = SmsHeader.fromByteArray(udh); 634 offset += userDataHeaderLength; 635 636 int headerBits = (userDataHeaderLength + 1) * 8; 637 headerSeptets = headerBits / 7; 638 headerSeptets += (headerBits % 7) > 0 ? 1 : 0; 639 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits; 640 } 641 642 int bufferLen; 643 if (dataInSeptets) { 644 /* 645 * Here we just create the user data length to be the remainder of 646 * the pdu minus the user data header, since userDataLength means 647 * the number of uncompressed septets. 648 */ 649 bufferLen = mPdu.length - offset; 650 } else { 651 /* 652 * userDataLength is the count of octets, so just subtract the 653 * user data header. 654 */ 655 bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0); 656 if (bufferLen < 0) { 657 bufferLen = 0; 658 } 659 } 660 661 mUserData = new byte[bufferLen]; 662 System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length); 663 mCur = offset; 664 665 if (dataInSeptets) { 666 // Return the number of septets 667 int count = userDataLength - headerSeptets; 668 // If count < 0, return 0 (means UDL was probably incorrect) 669 return count < 0 ? 0 : count; 670 } else { 671 // Return the number of octets 672 return mUserData.length; 673 } 674 } 675 676 /** 677 * Returns the user data payload, not including the headers 678 * 679 * @return the user data payload, not including the headers 680 */ 681 byte[] getUserData() { 682 return mUserData; 683 } 684 685 /** 686 * Returns an object representing the user data headers 687 * 688 * {@hide} 689 */ 690 SmsHeader getUserDataHeader() { 691 return mUserDataHeader; 692 } 693 694 /** 695 * Interprets the user data payload as packed GSM 7bit characters, and 696 * decodes them into a String. 697 * 698 * @param septetCount the number of septets in the user data payload 699 * @return a String with the decoded characters 700 */ 701 String getUserDataGSM7Bit(int septetCount, int languageTable, 702 int languageShiftTable) { 703 String ret; 704 705 ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount, 706 mUserDataSeptetPadding, languageTable, languageShiftTable); 707 708 mCur += (septetCount * 7) / 8; 709 710 return ret; 711 } 712 713 /** 714 * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's 715 * stored in 8-bit unpacked format) characters, and decodes them into a String. 716 * 717 * @param byteCount the number of byest in the user data payload 718 * @return a String with the decoded characters 719 */ 720 String getUserDataGSM8bit(int byteCount) { 721 String ret; 722 723 ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount); 724 725 mCur += byteCount; 726 727 return ret; 728 } 729 730 /** 731 * Interprets the user data payload as UCS2 characters, and 732 * decodes them into a String. 733 * 734 * @param byteCount the number of bytes in the user data payload 735 * @return a String with the decoded characters 736 */ 737 String getUserDataUCS2(int byteCount) { 738 String ret; 739 740 try { 741 ret = new String(mPdu, mCur, byteCount, "utf-16"); 742 } catch (UnsupportedEncodingException ex) { 743 ret = ""; 744 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex); 745 } 746 747 mCur += byteCount; 748 return ret; 749 } 750 751 /** 752 * Interprets the user data payload as KSC-5601 characters, and 753 * decodes them into a String. 754 * 755 * @param byteCount the number of bytes in the user data payload 756 * @return a String with the decoded characters 757 */ 758 String getUserDataKSC5601(int byteCount) { 759 String ret; 760 761 try { 762 ret = new String(mPdu, mCur, byteCount, "KSC5601"); 763 } catch (UnsupportedEncodingException ex) { 764 ret = ""; 765 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex); 766 } 767 768 mCur += byteCount; 769 return ret; 770 } 771 772 boolean moreDataPresent() { 773 return (mPdu.length > mCur); 774 } 775 } 776 777 /** 778 * Calculate the number of septets needed to encode the message. 779 * 780 * @param msgBody the message to encode 781 * @param use7bitOnly ignore (but still count) illegal characters if true 782 * @return TextEncodingDetails 783 */ 784 public static TextEncodingDetails calculateLength(CharSequence msgBody, 785 boolean use7bitOnly) { 786 TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly); 787 if (ted == null) { 788 ted = new TextEncodingDetails(); 789 int octets = msgBody.length() * 2; 790 ted.codeUnitCount = msgBody.length(); 791 if (octets > MAX_USER_DATA_BYTES) { 792 // If EMS is not supported, break down EMS into single segment SMS 793 // and add page info " x/y". 794 // In the case of UCS2 encoding type, we need 8 bytes for this 795 // but we only have 6 bytes from UDH, so truncate the limit for 796 // each segment by 2 bytes (1 char). 797 int max_user_data_bytes_with_header = MAX_USER_DATA_BYTES_WITH_HEADER; 798 if (!android.telephony.SmsMessage.hasEmsSupport()) { 799 // make sure total number of segments is less than 10 800 if (octets <= 9 * (max_user_data_bytes_with_header - 2)) 801 max_user_data_bytes_with_header -= 2; 802 } 803 804 ted.msgCount = (octets + (max_user_data_bytes_with_header - 1)) / 805 max_user_data_bytes_with_header; 806 ted.codeUnitsRemaining = ((ted.msgCount * 807 max_user_data_bytes_with_header) - octets) / 2; 808 } else { 809 ted.msgCount = 1; 810 ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2; 811 } 812 ted.codeUnitSize = ENCODING_16BIT; 813 } 814 return ted; 815 } 816 817 /** {@inheritDoc} */ 818 @Override 819 public int getProtocolIdentifier() { 820 return mProtocolIdentifier; 821 } 822 823 /** 824 * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages. 825 * @return the TP-DCS field of the SMS header 826 */ 827 int getDataCodingScheme() { 828 return mDataCodingScheme; 829 } 830 831 /** {@inheritDoc} */ 832 @Override 833 public boolean isReplace() { 834 return (mProtocolIdentifier & 0xc0) == 0x40 835 && (mProtocolIdentifier & 0x3f) > 0 836 && (mProtocolIdentifier & 0x3f) < 8; 837 } 838 839 /** {@inheritDoc} */ 840 @Override 841 public boolean isCphsMwiMessage() { 842 return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear() 843 || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet(); 844 } 845 846 /** {@inheritDoc} */ 847 @Override 848 public boolean isMWIClearMessage() { 849 if (mIsMwi && !mMwiSense) { 850 return true; 851 } 852 853 return mOriginatingAddress != null 854 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear(); 855 } 856 857 /** {@inheritDoc} */ 858 @Override 859 public boolean isMWISetMessage() { 860 if (mIsMwi && mMwiSense) { 861 return true; 862 } 863 864 return mOriginatingAddress != null 865 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet(); 866 } 867 868 /** {@inheritDoc} */ 869 @Override 870 public boolean isMwiDontStore() { 871 if (mIsMwi && mMwiDontStore) { 872 return true; 873 } 874 875 if (isCphsMwiMessage()) { 876 // See CPHS 4.2 Section B.4.2.1 877 // If the user data is a single space char, do not store 878 // the message. Otherwise, store and display as usual 879 if (" ".equals(getMessageBody())) { 880 return true; 881 } 882 } 883 884 return false; 885 } 886 887 /** {@inheritDoc} */ 888 @Override 889 public int getStatus() { 890 return mStatus; 891 } 892 893 /** {@inheritDoc} */ 894 @Override 895 public boolean isStatusReportMessage() { 896 return mIsStatusReportMessage; 897 } 898 899 /** {@inheritDoc} */ 900 @Override 901 public boolean isReplyPathPresent() { 902 return mReplyPathPresent; 903 } 904 905 /** 906 * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6] 907 * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format: 908 * ME/TA converts each octet of TP data unit into two IRA character long 909 * hex number (e.g. octet with integer value 42 is presented to TE as two 910 * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast, 911 * something else... 912 */ 913 private void parsePdu(byte[] pdu) { 914 mPdu = pdu; 915 // Rlog.d(LOG_TAG, "raw sms message:"); 916 // Rlog.d(LOG_TAG, s); 917 918 PduParser p = new PduParser(pdu); 919 920 mScAddress = p.getSCAddress(); 921 922 if (mScAddress != null) { 923 if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress); 924 } 925 926 // TODO(mkf) support reply path, user data header indicator 927 928 // TP-Message-Type-Indicator 929 // 9.2.3 930 int firstByte = p.getByte(); 931 932 mMti = firstByte & 0x3; 933 switch (mMti) { 934 // TP-Message-Type-Indicator 935 // 9.2.3 936 case 0: 937 case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved. 938 //This should be processed in the same way as MTI == 0 (Deliver) 939 parseSmsDeliver(p, firstByte); 940 break; 941 case 1: 942 parseSmsSubmit(p, firstByte); 943 break; 944 case 2: 945 parseSmsStatusReport(p, firstByte); 946 break; 947 default: 948 // TODO(mkf) the rest of these 949 throw new RuntimeException("Unsupported message type"); 950 } 951 } 952 953 /** 954 * Parses a SMS-STATUS-REPORT message. 955 * 956 * @param p A PduParser, cued past the first byte. 957 * @param firstByte The first byte of the PDU, which contains MTI, etc. 958 */ 959 private void parseSmsStatusReport(PduParser p, int firstByte) { 960 mIsStatusReportMessage = true; 961 962 // TP-Message-Reference 963 mMessageRef = p.getByte(); 964 // TP-Recipient-Address 965 mRecipientAddress = p.getAddress(); 966 // TP-Service-Centre-Time-Stamp 967 mScTimeMillis = p.getSCTimestampMillis(); 968 p.getSCTimestampMillis(); 969 // TP-Status 970 mStatus = p.getByte(); 971 972 // The following are optional fields that may or may not be present. 973 if (p.moreDataPresent()) { 974 // TP-Parameter-Indicator 975 int extraParams = p.getByte(); 976 int moreExtraParams = extraParams; 977 while ((moreExtraParams & 0x80) != 0) { 978 // We only know how to parse a few extra parameters, all 979 // indicated in the first TP-PI octet, so skip over any 980 // additional TP-PI octets. 981 moreExtraParams = p.getByte(); 982 } 983 // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator, 984 // only process the byte if the reserved bits (bits3 to 6) are zero. 985 if ((extraParams & 0x78) == 0) { 986 // TP-Protocol-Identifier 987 if ((extraParams & 0x01) != 0) { 988 mProtocolIdentifier = p.getByte(); 989 } 990 // TP-Data-Coding-Scheme 991 if ((extraParams & 0x02) != 0) { 992 mDataCodingScheme = p.getByte(); 993 } 994 // TP-User-Data-Length (implies existence of TP-User-Data) 995 if ((extraParams & 0x04) != 0) { 996 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; 997 parseUserData(p, hasUserDataHeader); 998 } 999 } 1000 } 1001 } 1002 1003 private void parseSmsDeliver(PduParser p, int firstByte) { 1004 mReplyPathPresent = (firstByte & 0x80) == 0x80; 1005 1006 mOriginatingAddress = p.getAddress(); 1007 1008 if (mOriginatingAddress != null) { 1009 if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " 1010 + mOriginatingAddress.address); 1011 } 1012 1013 // TP-Protocol-Identifier (TP-PID) 1014 // TS 23.040 9.2.3.9 1015 mProtocolIdentifier = p.getByte(); 1016 1017 // TP-Data-Coding-Scheme 1018 // see TS 23.038 1019 mDataCodingScheme = p.getByte(); 1020 1021 if (VDBG) { 1022 Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier 1023 + " data coding scheme: " + mDataCodingScheme); 1024 } 1025 1026 mScTimeMillis = p.getSCTimestampMillis(); 1027 1028 if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis); 1029 1030 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; 1031 1032 parseUserData(p, hasUserDataHeader); 1033 } 1034 1035 /** 1036 * Parses a SMS-SUBMIT message. 1037 * 1038 * @param p A PduParser, cued past the first byte. 1039 * @param firstByte The first byte of the PDU, which contains MTI, etc. 1040 */ 1041 private void parseSmsSubmit(PduParser p, int firstByte) { 1042 mReplyPathPresent = (firstByte & 0x80) == 0x80; 1043 1044 // TP-MR (TP-Message Reference) 1045 mMessageRef = p.getByte(); 1046 1047 mRecipientAddress = p.getAddress(); 1048 1049 if (mRecipientAddress != null) { 1050 if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address); 1051 } 1052 1053 // TP-Protocol-Identifier (TP-PID) 1054 // TS 23.040 9.2.3.9 1055 mProtocolIdentifier = p.getByte(); 1056 1057 // TP-Data-Coding-Scheme 1058 // see TS 23.038 1059 mDataCodingScheme = p.getByte(); 1060 1061 if (VDBG) { 1062 Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier 1063 + " data coding scheme: " + mDataCodingScheme); 1064 } 1065 1066 // TP-Validity-Period-Format 1067 int validityPeriodLength = 0; 1068 int validityPeriodFormat = ((firstByte>>3) & 0x3); 1069 if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/ 1070 { 1071 validityPeriodLength = 0; 1072 } 1073 else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/ 1074 { 1075 validityPeriodLength = 1; 1076 } 1077 else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/ 1078 { 1079 validityPeriodLength = 7; 1080 } 1081 1082 // TP-Validity-Period is not used on phone, so just ignore it for now. 1083 while (validityPeriodLength-- > 0) 1084 { 1085 p.getByte(); 1086 } 1087 1088 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; 1089 1090 parseUserData(p, hasUserDataHeader); 1091 } 1092 1093 /** 1094 * Parses the User Data of an SMS. 1095 * 1096 * @param p The current PduParser. 1097 * @param hasUserDataHeader Indicates whether a header is present in the 1098 * User Data. 1099 */ 1100 private void parseUserData(PduParser p, boolean hasUserDataHeader) { 1101 boolean hasMessageClass = false; 1102 boolean userDataCompressed = false; 1103 1104 int encodingType = ENCODING_UNKNOWN; 1105 1106 // Look up the data encoding scheme 1107 if ((mDataCodingScheme & 0x80) == 0) { 1108 userDataCompressed = (0 != (mDataCodingScheme & 0x20)); 1109 hasMessageClass = (0 != (mDataCodingScheme & 0x10)); 1110 1111 if (userDataCompressed) { 1112 Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme " 1113 + "(compression) " + (mDataCodingScheme & 0xff)); 1114 } else { 1115 switch ((mDataCodingScheme >> 2) & 0x3) { 1116 case 0: // GSM 7 bit default alphabet 1117 encodingType = ENCODING_7BIT; 1118 break; 1119 1120 case 2: // UCS 2 (16bit) 1121 encodingType = ENCODING_16BIT; 1122 break; 1123 1124 case 1: // 8 bit data 1125 //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string 1126 //that's stored in 8-bit unpacked format) characters. 1127 Resources r = Resources.getSystem(); 1128 if (r.getBoolean(com.android.internal. 1129 R.bool.config_sms_decode_gsm_8bit_data)) { 1130 encodingType = ENCODING_8BIT; 1131 break; 1132 } 1133 1134 case 3: // reserved 1135 Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme " 1136 + (mDataCodingScheme & 0xff)); 1137 encodingType = ENCODING_8BIT; 1138 break; 1139 } 1140 } 1141 } else if ((mDataCodingScheme & 0xf0) == 0xf0) { 1142 hasMessageClass = true; 1143 userDataCompressed = false; 1144 1145 if (0 == (mDataCodingScheme & 0x04)) { 1146 // GSM 7 bit default alphabet 1147 encodingType = ENCODING_7BIT; 1148 } else { 1149 // 8 bit data 1150 encodingType = ENCODING_8BIT; 1151 } 1152 } else if ((mDataCodingScheme & 0xF0) == 0xC0 1153 || (mDataCodingScheme & 0xF0) == 0xD0 1154 || (mDataCodingScheme & 0xF0) == 0xE0) { 1155 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 1156 1157 // 0xC0 == 7 bit, don't store 1158 // 0xD0 == 7 bit, store 1159 // 0xE0 == UCS-2, store 1160 1161 if ((mDataCodingScheme & 0xF0) == 0xE0) { 1162 encodingType = ENCODING_16BIT; 1163 } else { 1164 encodingType = ENCODING_7BIT; 1165 } 1166 1167 userDataCompressed = false; 1168 boolean active = ((mDataCodingScheme & 0x08) == 0x08); 1169 // bit 0x04 reserved 1170 1171 // VM - If TP-UDH is present, these values will be overwritten 1172 if ((mDataCodingScheme & 0x03) == 0x00) { 1173 mIsMwi = true; /* Indicates vmail */ 1174 mMwiSense = active;/* Indicates vmail notification set/clear */ 1175 mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0); 1176 1177 /* Set voice mail count based on notification bit */ 1178 if (active == true) { 1179 mVoiceMailCount = -1; // unknown number of messages waiting 1180 } else { 1181 mVoiceMailCount = 0; // no unread messages 1182 } 1183 1184 Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = " 1185 + (mDataCodingScheme & 0xff) + " Dont store = " 1186 + mMwiDontStore + " vmail count = " + mVoiceMailCount); 1187 1188 } else { 1189 mIsMwi = false; 1190 Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: " 1191 + (mDataCodingScheme & 0xff)); 1192 } 1193 } else if ((mDataCodingScheme & 0xC0) == 0x80) { 1194 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 1195 // 0x80..0xBF == Reserved coding groups 1196 if (mDataCodingScheme == 0x84) { 1197 // This value used for KSC5601 by carriers in Korea. 1198 encodingType = ENCODING_KSC5601; 1199 } else { 1200 Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme " 1201 + (mDataCodingScheme & 0xff)); 1202 } 1203 } else { 1204 Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme " 1205 + (mDataCodingScheme & 0xff)); 1206 } 1207 1208 // set both the user data and the user data header. 1209 int count = p.constructUserData(hasUserDataHeader, 1210 encodingType == ENCODING_7BIT); 1211 this.mUserData = p.getUserData(); 1212 this.mUserDataHeader = p.getUserDataHeader(); 1213 1214 /* 1215 * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24 1216 * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND) 1217 * ieidl =2 octets 1218 * ieda msg_ind_type = 0x00 (voice mail; discard sms )or 1219 * = 0x80 (voice mail; store sms) 1220 * msg_count = 0x00 ..0xFF 1221 */ 1222 if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) { 1223 for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) { 1224 int msgInd = msg.msgIndType & 0xff; 1225 /* 1226 * TS 23.040 V6.8.1 Sec 9.2.3.24.2 1227 * bits 1 0 : basic message indication type 1228 * bits 4 3 2 : extended message indication type 1229 * bits 6 5 : Profile id bit 7 storage type 1230 */ 1231 if ((msgInd == 0) || (msgInd == 0x80)) { 1232 mIsMwi = true; 1233 if (msgInd == 0x80) { 1234 /* Store message because TP_UDH indicates so*/ 1235 mMwiDontStore = false; 1236 } else if (mMwiDontStore == false) { 1237 /* Storage bit is not set by TP_UDH 1238 * Check for conflict 1239 * between message storage bit in TP_UDH 1240 * & DCS. The message shall be stored if either of 1241 * the one indicates so. 1242 * TS 23.040 V6.8.1 Sec 9.2.3.24.2 1243 */ 1244 if (!((((mDataCodingScheme & 0xF0) == 0xD0) 1245 || ((mDataCodingScheme & 0xF0) == 0xE0)) 1246 && ((mDataCodingScheme & 0x03) == 0x00))) { 1247 /* Even DCS did not have voice mail with Storage bit 1248 * 3GPP TS 23.038 V7.0.0 section 4 1249 * So clear this flag*/ 1250 mMwiDontStore = true; 1251 } 1252 } 1253 1254 mVoiceMailCount = msg.msgCount & 0xff; 1255 1256 /* 1257 * In the event of a conflict between message count setting 1258 * and DCS then the Message Count in the TP-UDH shall 1259 * override the indication in the TP-DCS. Set voice mail 1260 * notification based on count in TP-UDH 1261 */ 1262 if (mVoiceMailCount > 0) 1263 mMwiSense = true; 1264 else 1265 mMwiSense = false; 1266 1267 Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd 1268 + " Dont store = " + mMwiDontStore + " Vmail count = " 1269 + mVoiceMailCount); 1270 1271 /* 1272 * There can be only one IE for each type of message 1273 * indication in TP_UDH. In the event they are duplicated 1274 * last occurence will be used. Hence the for loop 1275 */ 1276 } else { 1277 Rlog.w(LOG_TAG, "TP_UDH fax/email/" 1278 + "extended msg/multisubscriber profile. Msg Ind = " + msgInd); 1279 } 1280 } // end of for 1281 } // end of if UDH 1282 1283 switch (encodingType) { 1284 case ENCODING_UNKNOWN: 1285 mMessageBody = null; 1286 break; 1287 1288 case ENCODING_8BIT: 1289 //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string 1290 //that's stored in 8-bit unpacked format) characters. 1291 Resources r = Resources.getSystem(); 1292 if (r.getBoolean(com.android.internal. 1293 R.bool.config_sms_decode_gsm_8bit_data)) { 1294 mMessageBody = p.getUserDataGSM8bit(count); 1295 } else { 1296 mMessageBody = null; 1297 } 1298 break; 1299 1300 case ENCODING_7BIT: 1301 mMessageBody = p.getUserDataGSM7Bit(count, 1302 hasUserDataHeader ? mUserDataHeader.languageTable : 0, 1303 hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0); 1304 break; 1305 1306 case ENCODING_16BIT: 1307 mMessageBody = p.getUserDataUCS2(count); 1308 break; 1309 1310 case ENCODING_KSC5601: 1311 mMessageBody = p.getUserDataKSC5601(count); 1312 break; 1313 } 1314 1315 if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'"); 1316 1317 if (mMessageBody != null) { 1318 parseMessageBody(); 1319 } 1320 1321 if (!hasMessageClass) { 1322 messageClass = MessageClass.UNKNOWN; 1323 } else { 1324 switch (mDataCodingScheme & 0x3) { 1325 case 0: 1326 messageClass = MessageClass.CLASS_0; 1327 break; 1328 case 1: 1329 messageClass = MessageClass.CLASS_1; 1330 break; 1331 case 2: 1332 messageClass = MessageClass.CLASS_2; 1333 break; 1334 case 3: 1335 messageClass = MessageClass.CLASS_3; 1336 break; 1337 } 1338 } 1339 } 1340 1341 /** 1342 * {@inheritDoc} 1343 */ 1344 @Override 1345 public MessageClass getMessageClass() { 1346 return messageClass; 1347 } 1348 1349 /** 1350 * Returns true if this is a (U)SIM data download type SM. 1351 * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9. 1352 * 1353 * @return true if this is a USIM data download message; false otherwise 1354 */ 1355 boolean isUsimDataDownload() { 1356 return messageClass == MessageClass.CLASS_2 && 1357 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c); 1358 } 1359 1360 public int getNumOfVoicemails() { 1361 /* 1362 * Order of priority if multiple indications are present is 1.UDH, 1363 * 2.DCS, 3.CPHS. 1364 * Voice mail count if voice mail present indication is 1365 * received 1366 * 1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040] 1367 * 2. DCS only: count is unknown mVoiceMailCount= -1 1368 * 3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700] 1369 * Voice mail clear, mVoiceMailCount = 0. 1370 */ 1371 if ((!mIsMwi) && isCphsMwiMessage()) { 1372 if (mOriginatingAddress != null 1373 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) { 1374 mVoiceMailCount = 0xff; 1375 } else { 1376 mVoiceMailCount = 0; 1377 } 1378 Rlog.v(LOG_TAG, "CPHS voice mail message"); 1379 } 1380 return mVoiceMailCount; 1381 } 1382} 1383