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