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