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