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