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