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