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