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