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