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