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