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