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