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 static com.android.internal.telephony.SmsConstants.ENCODING_16BIT; 20 import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT; 21 import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT; 22 import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601; 23 import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN; 24 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES; 25 import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS; 26 import static com.android.internal.telephony.SmsConstants.MessageClass; 27 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.res.Resources; 30 import android.os.Build; 31 import android.telephony.PhoneNumberUtils; 32 import android.text.TextUtils; 33 34 import com.android.internal.telephony.EncodeException; 35 import com.android.internal.telephony.GsmAlphabet; 36 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 37 import com.android.internal.telephony.Sms7BitEncodingTranslator; 38 import com.android.internal.telephony.SmsHeader; 39 import com.android.internal.telephony.SmsMessageBase; 40 import com.android.internal.telephony.uicc.IccUtils; 41 import com.android.telephony.Rlog; 42 43 import java.io.ByteArrayOutputStream; 44 import java.io.UnsupportedEncodingException; 45 import java.text.ParseException; 46 import java.time.DateTimeException; 47 import java.time.Instant; 48 import java.time.LocalDateTime; 49 import java.time.ZoneId; 50 import java.time.ZoneOffset; 51 import java.time.ZonedDateTime; 52 53 /** 54 * A Short Message Service message. 55 * 56 */ 57 public class SmsMessage extends SmsMessageBase { 58 static final String LOG_TAG = "SmsMessage"; 59 private static final boolean VDBG = false; 60 61 private MessageClass messageClass; 62 63 /** 64 * TP-Message-Type-Indicator 65 * 9.2.3 66 */ 67 private int mMti; 68 69 /** TP-Protocol-Identifier (TP-PID) */ 70 private int mProtocolIdentifier; 71 72 // TP-Data-Coding-Scheme 73 // see TS 23.038 74 private int mDataCodingScheme; 75 76 // TP-Reply-Path 77 // e.g. 23.040 9.2.2.1 78 private boolean mReplyPathPresent = false; 79 80 /** 81 * TP-Status - status of a previously submitted SMS. 82 * This field applies to SMS-STATUS-REPORT messages. 0 indicates success; 83 * see TS 23.040, 9.2.3.15 for description of other possible values. 84 */ 85 private int mStatus; 86 87 /** 88 * TP-Status - status of a previously submitted SMS. 89 * This field is true iff the message is a SMS-STATUS-REPORT message. 90 */ 91 private boolean mIsStatusReportMessage = false; 92 93 private int mVoiceMailCount = 0; 94 95 /** TP-Validity-Period-Format (TP-VPF). See TS 23.040, 9.2.3.3 */ 96 private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00; 97 private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01; 98 private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02; 99 private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03; 100 101 // Validity Period min - 5 mins 102 private static final int VALIDITY_PERIOD_MIN = 5; 103 // Validity Period max - 63 weeks 104 private static final int VALIDITY_PERIOD_MAX = 635040; 105 106 private static final int INVALID_VALIDITY_PERIOD = -1; 107 108 @UnsupportedAppUsage SmsMessage()109 public SmsMessage() { 110 } 111 112 public static class SubmitPdu extends SubmitPduBase { 113 @UnsupportedAppUsage SubmitPdu()114 public SubmitPdu() { 115 } 116 } 117 118 /** 119 * Create an SmsMessage from a raw PDU. 120 */ 121 @UnsupportedAppUsage createFromPdu(byte[] pdu)122 public static SmsMessage createFromPdu(byte[] pdu) { 123 try { 124 SmsMessage msg = new SmsMessage(); 125 msg.parsePdu(pdu); 126 return msg; 127 } catch (RuntimeException ex) { 128 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 129 return null; 130 } catch (OutOfMemoryError e) { 131 Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e); 132 return null; 133 } 134 } 135 136 /** 137 * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated 138 * by TP_PID field set to value 0x40 139 */ isTypeZero()140 public boolean isTypeZero() { 141 return (mProtocolIdentifier == 0x40); 142 } 143 144 /** 145 * Creates an SmsMessage from an SMS EF record. 146 * 147 * @param index Index of SMS EF record. 148 * @param data Record data. 149 * @return An SmsMessage representing the record. 150 * 151 * @hide 152 */ 153 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " 154 + "android.telephony.SmsMessage} API instead") createFromEfRecord(int index, byte[] data)155 public static SmsMessage createFromEfRecord(int index, byte[] data) { 156 try { 157 SmsMessage msg = new SmsMessage(); 158 159 msg.mIndexOnIcc = index; 160 161 // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT, 162 // or STORED_UNSENT 163 // See TS 51.011 10.5.3 164 if ((data[0] & 1) == 0) { 165 Rlog.w(LOG_TAG, 166 "SMS parsing failed: Trying to parse a free record"); 167 return null; 168 } else { 169 msg.mStatusOnIcc = data[0] & 0x07; 170 } 171 172 int size = data.length - 1; 173 174 // Note: Data may include trailing FF's. That's OK; message 175 // should still parse correctly. 176 byte[] pdu = new byte[size]; 177 System.arraycopy(data, 1, pdu, 0, size); 178 msg.parsePdu(pdu); 179 return msg; 180 } catch (RuntimeException ex) { 181 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 182 return null; 183 } 184 } 185 186 /** 187 * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the 188 * length in bytes (not hex chars) less the SMSC header 189 */ getTPLayerLengthForPDU(String pdu)190 public static int getTPLayerLengthForPDU(String pdu) { 191 int len = pdu.length() / 2; 192 int smscLen = Integer.parseInt(pdu.substring(0, 2), 16); 193 194 return len - smscLen - 1; 195 } 196 197 /** 198 * Gets Encoded Relative Validity Period Value from Validity period in mins. 199 * 200 * @param validityPeriod Validity period in mins. 201 * 202 * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. 203 * ------------------------------------------------------------ 204 * TP-VP | Validity period 205 * (Relative format) | value 206 * ------------------------------------------------------------ 207 * 0 to 143 | (TP-VP + 1) x 5 minutes 208 * 144 to 167 | 12 hours + ((TP-VP -143) x 30 minutes) 209 * 168 to 196 | (TP-VP - 166) x 1 day 210 * 197 to 255 | (TP-VP - 192) x 1 week 211 * ------------------------------------------------------------ 212 * 213 * @return relValidityPeriod Encoded Relative Validity Period Value. 214 * @hide 215 */ getRelativeValidityPeriod(int validityPeriod)216 public static int getRelativeValidityPeriod(int validityPeriod) { 217 int relValidityPeriod = INVALID_VALIDITY_PERIOD; 218 219 if (validityPeriod >= VALIDITY_PERIOD_MIN) { 220 if (validityPeriod <= 720) { 221 relValidityPeriod = (validityPeriod / 5) - 1; 222 } else if (validityPeriod <= 1440) { 223 relValidityPeriod = ((validityPeriod - 720) / 30) + 143; 224 } else if (validityPeriod <= 43200) { 225 relValidityPeriod = (validityPeriod / 1440) + 166; 226 } else if (validityPeriod <= VALIDITY_PERIOD_MAX) { 227 relValidityPeriod = (validityPeriod / 10080) + 192; 228 } 229 } 230 return relValidityPeriod; 231 } 232 233 /** 234 * Gets an SMS-SUBMIT PDU for a destination address and a message. 235 * 236 * @param scAddress Service Centre address. Null means use default. 237 * @param destinationAddress the address of the destination for the message. 238 * @param message string representation of the message payload. 239 * @param statusReportRequested indicates whether a report is reuested for this message. 240 * @param header a byte array containing the data for the User Data Header. 241 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 242 * encoded message. Returns null on encode error. 243 * @hide 244 */ 245 @UnsupportedAppUsage getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header)246 public static SubmitPdu getSubmitPdu(String scAddress, 247 String destinationAddress, String message, 248 boolean statusReportRequested, byte[] header) { 249 return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header, 250 ENCODING_UNKNOWN, 0, 0); 251 } 252 253 254 /** 255 * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding. 256 * 257 * @param scAddress Service Centre address. Null means use default. 258 * @param destinationAddress the address of the destination for the message. 259 * @param message string representation of the message payload. 260 * @param statusReportRequested indicates whether a report is reuested for this message. 261 * @param header a byte array containing the data for the User Data Header. 262 * @param encoding encoding defined by constants in 263 * com.android.internal.telephony.SmsConstants.ENCODING_* 264 * @param languageTable 265 * @param languageShiftTable 266 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 267 * encoded message. Returns null on encode error. 268 * @hide 269 */ 270 @UnsupportedAppUsage getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header, int encoding, int languageTable, int languageShiftTable)271 public static SubmitPdu getSubmitPdu(String scAddress, 272 String destinationAddress, String message, 273 boolean statusReportRequested, byte[] header, int encoding, 274 int languageTable, int languageShiftTable) { 275 return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, 276 header, encoding, languageTable, languageShiftTable, -1); 277 } 278 279 /** 280 * Gets an SMS-SUBMIT PDU for a destination address and a message using the specified encoding. 281 * 282 * @param scAddress Service Centre address. Null means use default. 283 * @param destinationAddress the address of the destination for the message. 284 * @param message string representation of the message payload. 285 * @param statusReportRequested indicates whether a report is reuested for this message. 286 * @param header a byte array containing the data for the User Data Header. 287 * @param encoding encoding defined by constants in 288 * com.android.internal.telephony.SmsConstants.ENCODING_* 289 * @param languageTable 290 * @param languageShiftTable 291 * @param validityPeriod Validity Period of the message in Minutes. 292 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 293 * encoded message. Returns null on encode error. 294 * @hide 295 */ 296 @UnsupportedAppUsage getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, byte[] header, int encoding, int languageTable, int languageShiftTable, int validityPeriod)297 public static SubmitPdu getSubmitPdu(String scAddress, 298 String destinationAddress, String message, 299 boolean statusReportRequested, byte[] header, int encoding, 300 int languageTable, int languageShiftTable, int validityPeriod) { 301 302 // Perform null parameter checks. 303 if (message == null || destinationAddress == null) { 304 return null; 305 } 306 307 if (encoding == ENCODING_UNKNOWN) { 308 // Find the best encoding to use 309 TextEncodingDetails ted = calculateLength(message, false); 310 encoding = ted.codeUnitSize; 311 languageTable = ted.languageTable; 312 languageShiftTable = ted.languageShiftTable; 313 314 if (encoding == ENCODING_7BIT && 315 (languageTable != 0 || languageShiftTable != 0)) { 316 if (header != null) { 317 SmsHeader smsHeader = SmsHeader.fromByteArray(header); 318 if (smsHeader.languageTable != languageTable 319 || smsHeader.languageShiftTable != languageShiftTable) { 320 Rlog.w(LOG_TAG, "Updating language table in SMS header: " 321 + smsHeader.languageTable + " -> " + languageTable + ", " 322 + smsHeader.languageShiftTable + " -> " + languageShiftTable); 323 smsHeader.languageTable = languageTable; 324 smsHeader.languageShiftTable = languageShiftTable; 325 header = SmsHeader.toByteArray(smsHeader); 326 } 327 } else { 328 SmsHeader smsHeader = new SmsHeader(); 329 smsHeader.languageTable = languageTable; 330 smsHeader.languageShiftTable = languageShiftTable; 331 header = SmsHeader.toByteArray(smsHeader); 332 } 333 } 334 } 335 336 SubmitPdu ret = new SubmitPdu(); 337 338 int relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod); 339 340 byte mtiByte = 0x01; // SMS-SUBMIT 341 342 if (header != null) { 343 // Set TP-UDHI 344 mtiByte |= 0x40; 345 } 346 347 if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) { 348 // Set TP-Validity-Period-Format (TP-VPF) 349 mtiByte |= VALIDITY_PERIOD_FORMAT_RELATIVE << 3; 350 } 351 352 ByteArrayOutputStream bo = getSubmitPduHead( 353 scAddress, destinationAddress, mtiByte, 354 statusReportRequested, ret); 355 356 // Skip encoding pdu if error occurs when create pdu head and the error will be handled 357 // properly later on encodedMessage correctness check. 358 if (bo == null) return ret; 359 360 // User Data (and length) 361 byte[] userData; 362 try { 363 if (encoding == ENCODING_7BIT) { 364 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 365 languageTable, languageShiftTable); 366 } else { //assume UCS-2 367 try { 368 userData = encodeUCS2(message, header); 369 } catch(UnsupportedEncodingException uex) { 370 Rlog.e(LOG_TAG, 371 "Implausible UnsupportedEncodingException ", 372 uex); 373 return null; 374 } 375 } 376 } catch (EncodeException ex) { 377 if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) { 378 Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex); 379 return null; 380 } else { 381 // Encoding to the 7-bit alphabet failed. Let's see if we can 382 // send it as a UCS-2 encoded message 383 try { 384 userData = encodeUCS2(message, header); 385 encoding = ENCODING_16BIT; 386 } catch (EncodeException ex1) { 387 Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1); 388 return null; 389 } catch (UnsupportedEncodingException uex) { 390 Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex); 391 return null; 392 } 393 } 394 } 395 396 if (encoding == ENCODING_7BIT) { 397 if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { 398 // Message too long 399 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)"); 400 return null; 401 } 402 // TP-Data-Coding-Scheme 403 // Default encoding, uncompressed 404 // To test writing messages to the SIM card, change this value 0x00 405 // to 0x12, which means "bits 1 and 0 contain message class, and the 406 // class is 2". Note that this takes effect for the sender. In other 407 // words, messages sent by the phone with this change will end up on 408 // the receiver's SIM card. You can then send messages to yourself 409 // (on a phone with this change) and they'll end up on the SIM card. 410 bo.write(0x00); 411 } else { // assume UCS-2 412 if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { 413 // Message too long 414 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)"); 415 return null; 416 } 417 // TP-Data-Coding-Scheme 418 // UCS-2 encoding, uncompressed 419 bo.write(0x08); 420 } 421 422 // TP-Validity-Period (TP-VP) 423 if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) { 424 bo.write(relativeValidityPeriod); 425 } 426 427 bo.write(userData, 0, userData.length); 428 ret.encodedMessage = bo.toByteArray(); 429 return ret; 430 } 431 432 /** 433 * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary 434 * 435 * @return encoded message as UCS2 436 * @throws UnsupportedEncodingException 437 * @throws EncodeException if String is too large to encode 438 */ 439 @UnsupportedAppUsage encodeUCS2(String message, byte[] header)440 private static byte[] encodeUCS2(String message, byte[] header) 441 throws UnsupportedEncodingException, EncodeException { 442 byte[] userData, textPart; 443 textPart = message.getBytes("utf-16be"); 444 445 if (header != null) { 446 // Need 1 byte for UDHL 447 userData = new byte[header.length + textPart.length + 1]; 448 449 userData[0] = (byte)header.length; 450 System.arraycopy(header, 0, userData, 1, header.length); 451 System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length); 452 } 453 else { 454 userData = textPart; 455 } 456 if (userData.length > 255) { 457 throw new EncodeException( 458 "Payload cannot exceed 255 bytes", EncodeException.ERROR_EXCEED_SIZE); 459 } 460 byte[] ret = new byte[userData.length+1]; 461 ret[0] = (byte) (userData.length & 0xff ); 462 System.arraycopy(userData, 0, ret, 1, userData.length); 463 return ret; 464 } 465 466 /** 467 * Gets an SMS-SUBMIT PDU for a destination address and a message. 468 * 469 * @param scAddress Service Centre address. Null means use default. 470 * @param destinationAddress the address of the destination for the message. 471 * @param message string representation of the message payload. 472 * @param statusReportRequested indicates whether a report is reuested for this message. 473 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 474 * encoded message. Returns null on encode error. 475 */ 476 @UnsupportedAppUsage getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested)477 public static SubmitPdu getSubmitPdu(String scAddress, 478 String destinationAddress, String message, 479 boolean statusReportRequested) { 480 481 return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null); 482 } 483 484 /** 485 * Gets an SMS-SUBMIT PDU for a destination address and a message. 486 * 487 * @param scAddress Service Centre address. Null means use default. 488 * @param destinationAddress the address of the destination for the message. 489 * @param message string representation of the message payload. 490 * @param statusReportRequested indicates whether a report is reuested for this message. 491 * @param validityPeriod Validity Period of the message in Minutes. 492 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 493 * encoded message. Returns null on encode error. 494 */ 495 @UnsupportedAppUsage getSubmitPdu(String scAddress, String destinationAddress, String message, boolean statusReportRequested, int validityPeriod)496 public static SubmitPdu getSubmitPdu(String scAddress, 497 String destinationAddress, String message, 498 boolean statusReportRequested, int validityPeriod) { 499 return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, 500 null, ENCODING_UNKNOWN, 0, 0, validityPeriod); 501 } 502 503 /** 504 * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. 505 * 506 * @param scAddress Service Centre address. Null means use default. 507 * @param destinationAddress the address of the destination for the message. 508 * @param destinationPort the port to deliver the message to at the destination. 509 * @param data the data for the message. 510 * @param statusReportRequested indicates whether a report is reuested for this message. 511 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 512 * encoded message. Returns null on encode error. 513 */ getSubmitPdu(String scAddress, String destinationAddress, int destinationPort, byte[] data, boolean statusReportRequested)514 public static SubmitPdu getSubmitPdu(String scAddress, 515 String destinationAddress, int destinationPort, byte[] data, 516 boolean statusReportRequested) { 517 518 SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs(); 519 portAddrs.destPort = destinationPort; 520 portAddrs.origPort = 0; 521 portAddrs.areEightBits = false; 522 523 SmsHeader smsHeader = new SmsHeader(); 524 smsHeader.portAddrs = portAddrs; 525 526 byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader); 527 528 if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) { 529 Rlog.e(LOG_TAG, "SMS data message may only contain " 530 + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes"); 531 return null; 532 } 533 534 SubmitPdu ret = new SubmitPdu(); 535 ByteArrayOutputStream bo = getSubmitPduHead( 536 scAddress, destinationAddress, (byte) 0x41, /* TP-MTI=SMS-SUBMIT, TP-UDHI=true */ 537 statusReportRequested, ret); 538 // Skip encoding pdu if error occurs when create pdu head and the error will be handled 539 // properly later on encodedMessage correctness check. 540 if (bo == null) return ret; 541 542 // TP-Data-Coding-Scheme 543 // No class, 8 bit data 544 bo.write(0x04); 545 546 // (no TP-Validity-Period) 547 548 // Total size 549 bo.write(data.length + smsHeaderData.length + 1); 550 551 // User data header 552 bo.write(smsHeaderData.length); 553 bo.write(smsHeaderData, 0, smsHeaderData.length); 554 555 // User data 556 bo.write(data, 0, data.length); 557 558 ret.encodedMessage = bo.toByteArray(); 559 return ret; 560 } 561 562 /** 563 * Creates the beginning of a SUBMIT PDU. 564 * 565 * This is the part of the SUBMIT PDU that is common to the two versions of 566 * {@link #getSubmitPdu}, one of which takes a byte array and the other of which takes a 567 * <code>String</code>. 568 * 569 * @param scAddress Service Centre address. Null means use default. 570 * @param destinationAddress the address of the destination for the message. 571 * @param mtiByte 572 * @param statusReportRequested indicates whether a report is reuested for this message. 573 * @param ret <code>SubmitPdu</code>. 574 * @return a byte array of the beginning of a SUBMIT PDU. Null for invalid destinationAddress. 575 */ 576 @UnsupportedAppUsage getSubmitPduHead( String scAddress, String destinationAddress, byte mtiByte, boolean statusReportRequested, SubmitPdu ret)577 private static ByteArrayOutputStream getSubmitPduHead( 578 String scAddress, String destinationAddress, byte mtiByte, 579 boolean statusReportRequested, SubmitPdu ret) { 580 ByteArrayOutputStream bo = new ByteArrayOutputStream( 581 MAX_USER_DATA_BYTES + 40); 582 583 // SMSC address with length octet, or 0 584 if (scAddress == null) { 585 ret.encodedScAddress = null; 586 } else { 587 ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength( 588 scAddress); 589 } 590 591 // TP-Message-Type-Indicator (and friends) 592 if (statusReportRequested) { 593 // Set TP-Status-Report-Request bit. 594 mtiByte |= 0x20; 595 if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested"); 596 } 597 bo.write(mtiByte); 598 599 // space for TP-Message-Reference 600 bo.write(0); 601 602 byte[] daBytes; 603 604 daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress); 605 606 // return empty pduHead for invalid destination address 607 if (daBytes == null) return null; 608 609 // destination address length in BCD digits, ignoring TON byte and pad 610 // TODO Should be better. 611 bo.write((daBytes.length - 1) * 2 612 - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); 613 614 // destination address 615 bo.write(daBytes, 0, daBytes.length); 616 617 // TP-Protocol-Identifier 618 bo.write(0); 619 return bo; 620 } 621 622 /** 623 * Gets an SMS-DELIVER PDU for an originating address and a message. 624 * 625 * @param scAddress Service Centre address. Null means use default. 626 * @param originatingAddress the address of the originating for the message. 627 * @param message string representation of the message payload. 628 * @param date the time stamp the message was received. 629 * @return a <code>SubmitPdu</code> containing the encoded SC address if applicable and the 630 * encoded message. Returns null on encode error. 631 * @hide 632 */ getDeliverPdu( String scAddress, String originatingAddress, String message, long date)633 public static SubmitPdu getDeliverPdu( 634 String scAddress, String originatingAddress, String message, long date) { 635 if (originatingAddress == null || message == null) { 636 return null; 637 } 638 639 // Find the best encoding to use 640 TextEncodingDetails ted = calculateLength(message, false); 641 int encoding = ted.codeUnitSize; 642 int languageTable = ted.languageTable; 643 int languageShiftTable = ted.languageShiftTable; 644 byte[] header = null; 645 646 if (encoding == ENCODING_7BIT && (languageTable != 0 || languageShiftTable != 0)) { 647 SmsHeader smsHeader = new SmsHeader(); 648 smsHeader.languageTable = languageTable; 649 smsHeader.languageShiftTable = languageShiftTable; 650 header = SmsHeader.toByteArray(smsHeader); 651 } 652 653 SubmitPdu ret = new SubmitPdu(); 654 655 ByteArrayOutputStream bo = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40); 656 657 // SMSC address with length octet, or 0 658 if (scAddress == null) { 659 ret.encodedScAddress = null; 660 } else { 661 ret.encodedScAddress = 662 PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(scAddress); 663 } 664 665 // TP-Message-Type-Indicator 666 bo.write(0); // SMS-DELIVER 667 668 byte[] oaBytes; 669 670 oaBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(originatingAddress); 671 672 // Return null for invalid originating address 673 if (oaBytes == null) return null; 674 675 // Originating address length in BCD digits, ignoring TON byte and pad 676 // TODO Should be better. 677 bo.write((oaBytes.length - 1) * 2 - ((oaBytes[oaBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); 678 679 // Originating Address 680 bo.write(oaBytes, 0, oaBytes.length); 681 682 // TP-Protocol-Identifier 683 bo.write(0); 684 685 // User Data (and length) 686 byte[] userData; 687 try { 688 if (encoding == ENCODING_7BIT) { 689 userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 690 languageTable, languageShiftTable); 691 } else { // Assume UCS-2 692 try { 693 userData = encodeUCS2(message, header); 694 } catch (UnsupportedEncodingException uex) { 695 Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex); 696 return null; 697 } 698 } 699 } catch (EncodeException ex) { 700 if (ex.getError() == EncodeException.ERROR_EXCEED_SIZE) { 701 Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex); 702 return null; 703 } else { 704 // Encoding to the 7-bit alphabet failed. Let's see if we can send it as a UCS-2 705 // encoded message 706 try { 707 userData = encodeUCS2(message, header); 708 encoding = ENCODING_16BIT; 709 } catch (EncodeException ex1) { 710 Rlog.e(LOG_TAG, "Exceed size limitation EncodeException", ex1); 711 return null; 712 } catch (UnsupportedEncodingException uex) { 713 Rlog.e(LOG_TAG, "Implausible UnsupportedEncodingException ", uex); 714 return null; 715 } 716 } 717 } 718 719 if (encoding == ENCODING_7BIT) { 720 if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { 721 // Message too long 722 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)"); 723 return null; 724 } 725 // TP-Data-Coding-Scheme 726 // Default encoding, uncompressed 727 bo.write(0x00); 728 } else { // Assume UCS-2 729 if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { 730 // Message too long 731 Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)"); 732 return null; 733 } 734 // TP-Data-Coding-Scheme 735 // UCS-2 encoding, uncompressed 736 bo.write(0x08); 737 } 738 739 // TP-Service-Centre-Time-Stamp 740 byte[] scts = new byte[7]; 741 742 ZonedDateTime zoneDateTime = Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault()); 743 LocalDateTime localDateTime = zoneDateTime.toLocalDateTime(); 744 745 // It indicates the difference, expressed in quarters of an hour, between the local time and 746 // GMT. 747 int timezoneOffset = zoneDateTime.getOffset().getTotalSeconds() / 60 / 15; 748 boolean negativeOffset = timezoneOffset < 0; 749 if (negativeOffset) { 750 timezoneOffset = -timezoneOffset; 751 } 752 int year = localDateTime.getYear(); 753 int month = localDateTime.getMonthValue(); 754 int day = localDateTime.getDayOfMonth(); 755 int hour = localDateTime.getHour(); 756 int minute = localDateTime.getMinute(); 757 int second = localDateTime.getSecond(); 758 759 year = year > 2000 ? year - 2000 : year - 1900; 760 scts[0] = (byte) ((((year % 10) & 0x0F) << 4) | ((year / 10) & 0x0F)); 761 scts[1] = (byte) ((((month % 10) & 0x0F) << 4) | ((month / 10) & 0x0F)); 762 scts[2] = (byte) ((((day % 10) & 0x0F) << 4) | ((day / 10) & 0x0F)); 763 scts[3] = (byte) ((((hour % 10) & 0x0F) << 4) | ((hour / 10) & 0x0F)); 764 scts[4] = (byte) ((((minute % 10) & 0x0F) << 4) | ((minute / 10) & 0x0F)); 765 scts[5] = (byte) ((((second % 10) & 0x0F) << 4) | ((second / 10) & 0x0F)); 766 scts[6] = (byte) ((((timezoneOffset % 10) & 0x0F) << 4) | ((timezoneOffset / 10) & 0x0F)); 767 if (negativeOffset) { 768 scts[0] |= 0x08; // Negative offset 769 } 770 bo.write(scts, 0, scts.length); 771 772 bo.write(userData, 0, userData.length); 773 ret.encodedMessage = bo.toByteArray(); 774 return ret; 775 } 776 777 private static class PduParser { 778 @UnsupportedAppUsage 779 byte mPdu[]; 780 @UnsupportedAppUsage 781 int mCur; 782 SmsHeader mUserDataHeader; 783 byte[] mUserData; 784 @UnsupportedAppUsage 785 int mUserDataSeptetPadding; 786 787 @UnsupportedAppUsage PduParser(byte[] pdu)788 PduParser(byte[] pdu) { 789 mPdu = pdu; 790 mCur = 0; 791 mUserDataSeptetPadding = 0; 792 } 793 794 /** 795 * Parse and return the SC address prepended to SMS messages coming via 796 * the TS 27.005 / AT interface. Returns null on invalid address 797 */ getSCAddress()798 String getSCAddress() { 799 int len; 800 String ret; 801 802 // length of SC Address 803 len = getByte(); 804 805 if (len == 0) { 806 // no SC address 807 ret = null; 808 } else { 809 // SC address 810 try { 811 ret = PhoneNumberUtils.calledPartyBCDToString( 812 mPdu, mCur, len, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY); 813 } catch (RuntimeException tr) { 814 Rlog.d(LOG_TAG, "invalid SC address: ", tr); 815 ret = null; 816 } 817 } 818 819 mCur += len; 820 821 return ret; 822 } 823 824 /** 825 * returns non-sign-extended byte value 826 */ 827 @UnsupportedAppUsage getByte()828 int getByte() { 829 return mPdu[mCur++] & 0xff; 830 } 831 832 /** 833 * Any address except the SC address (eg, originating address) See TS 834 * 23.040 9.1.2.5 835 */ getAddress()836 GsmSmsAddress getAddress() { 837 GsmSmsAddress ret; 838 839 // "The Address-Length field is an integer representation of 840 // the number field, i.e. excludes any semi-octet containing only 841 // fill bits." 842 // The TOA field is not included as part of this 843 int addressLength = mPdu[mCur] & 0xff; 844 int lengthBytes = 2 + (addressLength + 1) / 2; 845 846 try { 847 ret = new GsmSmsAddress(mPdu, mCur, lengthBytes); 848 } catch (ParseException e) { 849 ret = null; 850 //This is caught by createFromPdu(byte[] pdu) 851 throw new RuntimeException(e.getMessage()); 852 } 853 854 mCur += lengthBytes; 855 856 return ret; 857 } 858 859 /** 860 * Parses an SC timestamp and returns a currentTimeMillis()-style timestamp, or 0 if 861 * invalid. 862 */ getSCTimestampMillis()863 long getSCTimestampMillis() { 864 // TP-Service-Centre-Time-Stamp 865 int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 866 int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 867 int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 868 int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 869 int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 870 int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); 871 872 // For the timezone, the most significant bit of the 873 // least significant nibble is the sign byte 874 // (meaning the max range of this field is 79 quarter-hours, 875 // which is more than enough) 876 877 byte tzByte = mPdu[mCur++]; 878 879 // Mask out sign bit. 880 int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); 881 882 timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; 883 // timezoneOffset is in quarter hours. 884 int timeZoneOffsetSeconds = timezoneOffset * 15 * 60; 885 886 // It's 2006. Should I really support years < 2000? 887 int fullYear = year >= 90 ? year + 1900 : year + 2000; 888 try { 889 LocalDateTime localDateTime = LocalDateTime.of( 890 fullYear, 891 month /* 1-12 */, 892 day, 893 hour, 894 minute, 895 second); 896 long epochSeconds = 897 localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds; 898 // Convert to milliseconds. 899 return epochSeconds * 1000; 900 } catch (DateTimeException ex) { 901 Rlog.e(LOG_TAG, "Invalid timestamp", ex); 902 } 903 return 0; 904 } 905 906 /** 907 * Pulls the user data out of the PDU, and separates the payload from 908 * the header if there is one. 909 * 910 * @param hasUserDataHeader true if there is a user data header 911 * @param dataInSeptets true if the data payload is in septets instead 912 * of octets 913 * @return the number of septets or octets in the user data payload 914 */ constructUserData(boolean hasUserDataHeader, boolean dataInSeptets)915 int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) { 916 int offset = mCur; 917 int userDataLength = mPdu[offset++] & 0xff; 918 int headerSeptets = 0; 919 int userDataHeaderLength = 0; 920 921 if (hasUserDataHeader) { 922 userDataHeaderLength = mPdu[offset++] & 0xff; 923 924 byte[] udh = new byte[userDataHeaderLength]; 925 System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength); 926 mUserDataHeader = SmsHeader.fromByteArray(udh); 927 offset += userDataHeaderLength; 928 929 int headerBits = (userDataHeaderLength + 1) * 8; 930 headerSeptets = headerBits / 7; 931 headerSeptets += (headerBits % 7) > 0 ? 1 : 0; 932 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits; 933 } 934 935 int bufferLen; 936 if (dataInSeptets) { 937 /* 938 * Here we just create the user data length to be the remainder of 939 * the pdu minus the user data header, since userDataLength means 940 * the number of uncompressed septets. 941 */ 942 bufferLen = mPdu.length - offset; 943 } else { 944 /* 945 * userDataLength is the count of octets, so just subtract the 946 * user data header. 947 */ 948 bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0); 949 if (bufferLen < 0) { 950 bufferLen = 0; 951 } 952 } 953 954 mUserData = new byte[bufferLen]; 955 System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length); 956 mCur = offset; 957 958 if (dataInSeptets) { 959 // Return the number of septets 960 int count = userDataLength - headerSeptets; 961 // If count < 0, return 0 (means UDL was probably incorrect) 962 return count < 0 ? 0 : count; 963 } else { 964 // Return the number of octets 965 return mUserData.length; 966 } 967 } 968 969 /** 970 * Returns the user data payload, not including the headers 971 * 972 * @return the user data payload, not including the headers 973 */ 974 @UnsupportedAppUsage getUserData()975 byte[] getUserData() { 976 return mUserData; 977 } 978 979 /** 980 * Returns an object representing the user data headers 981 * 982 * {@hide} 983 */ getUserDataHeader()984 SmsHeader getUserDataHeader() { 985 return mUserDataHeader; 986 } 987 988 /** 989 * Interprets the user data payload as packed GSM 7bit characters, and 990 * decodes them into a String. 991 * 992 * @param septetCount the number of septets in the user data payload 993 * @return a String with the decoded characters 994 */ getUserDataGSM7Bit(int septetCount, int languageTable, int languageShiftTable)995 String getUserDataGSM7Bit(int septetCount, int languageTable, 996 int languageShiftTable) { 997 String ret; 998 999 ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount, 1000 mUserDataSeptetPadding, languageTable, languageShiftTable); 1001 1002 mCur += (septetCount * 7) / 8; 1003 1004 return ret; 1005 } 1006 1007 /** 1008 * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's 1009 * stored in 8-bit unpacked format) characters, and decodes them into a String. 1010 * 1011 * @param byteCount the number of byest in the user data payload 1012 * @return a String with the decoded characters 1013 */ getUserDataGSM8bit(int byteCount)1014 String getUserDataGSM8bit(int byteCount) { 1015 String ret; 1016 1017 ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount); 1018 1019 mCur += byteCount; 1020 1021 return ret; 1022 } 1023 1024 /** 1025 * Interprets the user data payload as UCS2 characters, and 1026 * decodes them into a String. 1027 * 1028 * @param byteCount the number of bytes in the user data payload 1029 * @return a String with the decoded characters 1030 */ 1031 @UnsupportedAppUsage getUserDataUCS2(int byteCount)1032 String getUserDataUCS2(int byteCount) { 1033 String ret; 1034 1035 try { 1036 ret = new String(mPdu, mCur, byteCount, "utf-16"); 1037 } catch (UnsupportedEncodingException ex) { 1038 ret = ""; 1039 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex); 1040 } 1041 1042 mCur += byteCount; 1043 return ret; 1044 } 1045 1046 /** 1047 * Interprets the user data payload as KSC-5601 characters, and 1048 * decodes them into a String. 1049 * 1050 * @param byteCount the number of bytes in the user data payload 1051 * @return a String with the decoded characters 1052 */ getUserDataKSC5601(int byteCount)1053 String getUserDataKSC5601(int byteCount) { 1054 String ret; 1055 1056 try { 1057 ret = new String(mPdu, mCur, byteCount, "KSC5601"); 1058 } catch (UnsupportedEncodingException ex) { 1059 ret = ""; 1060 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex); 1061 } 1062 1063 mCur += byteCount; 1064 return ret; 1065 } 1066 moreDataPresent()1067 boolean moreDataPresent() { 1068 return (mPdu.length > mCur); 1069 } 1070 } 1071 1072 /** 1073 * Calculates the number of SMS's required to encode the message body and 1074 * the number of characters remaining until the next message. 1075 * 1076 * @param msgBody the message to encode 1077 * @param use7bitOnly ignore (but still count) illegal characters if true 1078 * @return TextEncodingDetails 1079 */ 1080 @UnsupportedAppUsage calculateLength(CharSequence msgBody, boolean use7bitOnly)1081 public static TextEncodingDetails calculateLength(CharSequence msgBody, 1082 boolean use7bitOnly) { 1083 CharSequence newMsgBody = null; 1084 Resources r = Resources.getSystem(); 1085 if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) { 1086 newMsgBody = Sms7BitEncodingTranslator.translate(msgBody, false /* isCdmaFormat */); 1087 } 1088 if (TextUtils.isEmpty(newMsgBody)) { 1089 newMsgBody = msgBody; 1090 } 1091 TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly); 1092 if (ted == null) { 1093 return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody); 1094 } 1095 return ted; 1096 } 1097 1098 /** {@inheritDoc} */ 1099 @Override getProtocolIdentifier()1100 public int getProtocolIdentifier() { 1101 return mProtocolIdentifier; 1102 } 1103 1104 /** 1105 * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages. 1106 * @return the TP-DCS field of the SMS header 1107 */ getDataCodingScheme()1108 int getDataCodingScheme() { 1109 return mDataCodingScheme; 1110 } 1111 1112 /** {@inheritDoc} */ 1113 @Override isReplace()1114 public boolean isReplace() { 1115 return (mProtocolIdentifier & 0xc0) == 0x40 1116 && (mProtocolIdentifier & 0x3f) > 0 1117 && (mProtocolIdentifier & 0x3f) < 8; 1118 } 1119 1120 /** {@inheritDoc} */ 1121 @Override isCphsMwiMessage()1122 public boolean isCphsMwiMessage() { 1123 return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear() 1124 || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet(); 1125 } 1126 1127 /** {@inheritDoc} */ 1128 @UnsupportedAppUsage 1129 @Override isMWIClearMessage()1130 public boolean isMWIClearMessage() { 1131 if (mIsMwi && !mMwiSense) { 1132 return true; 1133 } 1134 1135 return mOriginatingAddress != null 1136 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear(); 1137 } 1138 1139 /** {@inheritDoc} */ 1140 @UnsupportedAppUsage 1141 @Override isMWISetMessage()1142 public boolean isMWISetMessage() { 1143 if (mIsMwi && mMwiSense) { 1144 return true; 1145 } 1146 1147 return mOriginatingAddress != null 1148 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet(); 1149 } 1150 1151 /** {@inheritDoc} */ 1152 @UnsupportedAppUsage 1153 @Override isMwiDontStore()1154 public boolean isMwiDontStore() { 1155 if (mIsMwi && mMwiDontStore) { 1156 return true; 1157 } 1158 1159 if (isCphsMwiMessage()) { 1160 // See CPHS 4.2 Section B.4.2.1 1161 // If the user data is a single space char, do not store 1162 // the message. Otherwise, store and display as usual 1163 if (" ".equals(getMessageBody())) { 1164 return true; 1165 } 1166 } 1167 1168 return false; 1169 } 1170 1171 /** {@inheritDoc} */ 1172 @UnsupportedAppUsage 1173 @Override getStatus()1174 public int getStatus() { 1175 return mStatus; 1176 } 1177 1178 /** {@inheritDoc} */ 1179 @UnsupportedAppUsage 1180 @Override isStatusReportMessage()1181 public boolean isStatusReportMessage() { 1182 return mIsStatusReportMessage; 1183 } 1184 1185 /** {@inheritDoc} */ 1186 @Override isReplyPathPresent()1187 public boolean isReplyPathPresent() { 1188 return mReplyPathPresent; 1189 } 1190 1191 /** 1192 * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6] 1193 * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format: 1194 * ME/TA converts each octet of TP data unit into two IRA character long 1195 * hex number (e.g. octet with integer value 42 is presented to TE as two 1196 * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast, 1197 * something else... 1198 */ parsePdu(byte[] pdu)1199 private void parsePdu(byte[] pdu) { 1200 mPdu = pdu; 1201 // Rlog.d(LOG_TAG, "raw sms message:"); 1202 // Rlog.d(LOG_TAG, s); 1203 1204 PduParser p = new PduParser(pdu); 1205 1206 mScAddress = p.getSCAddress(); 1207 1208 if (mScAddress != null) { 1209 if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress); 1210 } 1211 1212 // TODO(mkf) support reply path, user data header indicator 1213 1214 // TP-Message-Type-Indicator 1215 // 9.2.3 1216 int firstByte = p.getByte(); 1217 1218 mMti = firstByte & 0x3; 1219 switch (mMti) { 1220 // TP-Message-Type-Indicator 1221 // 9.2.3 1222 case 0: 1223 case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved. 1224 //This should be processed in the same way as MTI == 0 (Deliver) 1225 parseSmsDeliver(p, firstByte); 1226 break; 1227 case 1: 1228 parseSmsSubmit(p, firstByte); 1229 break; 1230 case 2: 1231 parseSmsStatusReport(p, firstByte); 1232 break; 1233 default: 1234 // TODO(mkf) the rest of these 1235 throw new RuntimeException("Unsupported message type"); 1236 } 1237 } 1238 1239 /** 1240 * Parses a SMS-STATUS-REPORT message. 1241 * 1242 * @param p A PduParser, cued past the first byte. 1243 * @param firstByte The first byte of the PDU, which contains MTI, etc. 1244 */ parseSmsStatusReport(PduParser p, int firstByte)1245 private void parseSmsStatusReport(PduParser p, int firstByte) { 1246 mIsStatusReportMessage = true; 1247 1248 // TP-Message-Reference 1249 mMessageRef = p.getByte(); 1250 // TP-Recipient-Address 1251 mRecipientAddress = p.getAddress(); 1252 // TP-Service-Centre-Time-Stamp 1253 mScTimeMillis = p.getSCTimestampMillis(); 1254 // TP-Discharge-Time 1255 p.getSCTimestampMillis(); 1256 // TP-Status 1257 mStatus = p.getByte(); 1258 1259 // The following are optional fields that may or may not be present. 1260 if (p.moreDataPresent()) { 1261 // TP-Parameter-Indicator 1262 int extraParams = p.getByte(); 1263 int moreExtraParams = extraParams; 1264 while ((moreExtraParams & 0x80) != 0) { 1265 // We only know how to parse a few extra parameters, all 1266 // indicated in the first TP-PI octet, so skip over any 1267 // additional TP-PI octets. 1268 moreExtraParams = p.getByte(); 1269 } 1270 // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator, 1271 // only process the byte if the reserved bits (bits3 to 6) are zero. 1272 if ((extraParams & 0x78) == 0) { 1273 // TP-Protocol-Identifier 1274 if ((extraParams & 0x01) != 0) { 1275 mProtocolIdentifier = p.getByte(); 1276 } 1277 // TP-Data-Coding-Scheme 1278 if ((extraParams & 0x02) != 0) { 1279 mDataCodingScheme = p.getByte(); 1280 } 1281 // TP-User-Data-Length (implies existence of TP-User-Data) 1282 if ((extraParams & 0x04) != 0) { 1283 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; 1284 parseUserData(p, hasUserDataHeader); 1285 } 1286 } 1287 } 1288 } 1289 parseSmsDeliver(PduParser p, int firstByte)1290 private void parseSmsDeliver(PduParser p, int firstByte) { 1291 mReplyPathPresent = (firstByte & 0x80) == 0x80; 1292 1293 mOriginatingAddress = p.getAddress(); 1294 1295 if (mOriginatingAddress != null) { 1296 if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " 1297 + mOriginatingAddress.address); 1298 } 1299 1300 // TP-Protocol-Identifier (TP-PID) 1301 // TS 23.040 9.2.3.9 1302 mProtocolIdentifier = p.getByte(); 1303 1304 // TP-Data-Coding-Scheme 1305 // see TS 23.038 1306 mDataCodingScheme = p.getByte(); 1307 1308 if (VDBG) { 1309 Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier 1310 + " data coding scheme: " + mDataCodingScheme); 1311 } 1312 1313 // TP-Service-Centre-Time-Stamp 1314 mScTimeMillis = p.getSCTimestampMillis(); 1315 1316 if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis); 1317 1318 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; 1319 1320 parseUserData(p, hasUserDataHeader); 1321 } 1322 1323 /** 1324 * Parses a SMS-SUBMIT message. 1325 * 1326 * @param p A PduParser, cued past the first byte. 1327 * @param firstByte The first byte of the PDU, which contains MTI, etc. 1328 */ parseSmsSubmit(PduParser p, int firstByte)1329 private void parseSmsSubmit(PduParser p, int firstByte) { 1330 mReplyPathPresent = (firstByte & 0x80) == 0x80; 1331 1332 // TP-MR (TP-Message Reference) 1333 mMessageRef = p.getByte(); 1334 1335 mRecipientAddress = p.getAddress(); 1336 1337 if (mRecipientAddress != null) { 1338 if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address); 1339 } 1340 1341 // TP-Protocol-Identifier (TP-PID) 1342 // TS 23.040 9.2.3.9 1343 mProtocolIdentifier = p.getByte(); 1344 1345 // TP-Data-Coding-Scheme 1346 // see TS 23.038 1347 mDataCodingScheme = p.getByte(); 1348 1349 if (VDBG) { 1350 Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier 1351 + " data coding scheme: " + mDataCodingScheme); 1352 } 1353 1354 // TP-Validity-Period-Format 1355 int validityPeriodLength = 0; 1356 int validityPeriodFormat = ((firstByte >> 3) & 0x3); 1357 if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_NONE) { 1358 validityPeriodLength = 0; 1359 } else if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) { 1360 validityPeriodLength = 1; 1361 } else { // VALIDITY_PERIOD_FORMAT_ENHANCED or VALIDITY_PERIOD_FORMAT_ABSOLUTE 1362 validityPeriodLength = 7; 1363 } 1364 1365 // TP-Validity-Period is not used on phone, so just ignore it for now. 1366 while (validityPeriodLength-- > 0) { 1367 p.getByte(); 1368 } 1369 1370 boolean hasUserDataHeader = (firstByte & 0x40) == 0x40; 1371 1372 parseUserData(p, hasUserDataHeader); 1373 } 1374 1375 /** 1376 * Parses the User Data of an SMS. 1377 * 1378 * @param p The current PduParser. 1379 * @param hasUserDataHeader Indicates whether a header is present in the 1380 * User Data. 1381 */ parseUserData(PduParser p, boolean hasUserDataHeader)1382 private void parseUserData(PduParser p, boolean hasUserDataHeader) { 1383 boolean hasMessageClass = false; 1384 boolean userDataCompressed = false; 1385 1386 int encodingType = ENCODING_UNKNOWN; 1387 1388 Resources r = Resources.getSystem(); 1389 // Look up the data encoding scheme 1390 if ((mDataCodingScheme & 0x80) == 0) { 1391 userDataCompressed = (0 != (mDataCodingScheme & 0x20)); 1392 hasMessageClass = (0 != (mDataCodingScheme & 0x10)); 1393 1394 if (userDataCompressed) { 1395 Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme " 1396 + "(compression) " + (mDataCodingScheme & 0xff)); 1397 } else { 1398 switch ((mDataCodingScheme >> 2) & 0x3) { 1399 case 0: // GSM 7 bit default alphabet 1400 encodingType = ENCODING_7BIT; 1401 break; 1402 1403 case 2: // UCS 2 (16bit) 1404 encodingType = ENCODING_16BIT; 1405 break; 1406 1407 case 1: // 8 bit data 1408 //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string 1409 //that's stored in 8-bit unpacked format) characters. 1410 if (r.getBoolean(com.android.internal. 1411 R.bool.config_sms_decode_gsm_8bit_data)) { 1412 encodingType = ENCODING_8BIT; 1413 break; 1414 } 1415 1416 case 3: // reserved 1417 Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme " 1418 + (mDataCodingScheme & 0xff)); 1419 encodingType = r.getInteger( 1420 com.android.internal.R.integer.default_reserved_data_coding_scheme); 1421 break; 1422 } 1423 } 1424 } else if ((mDataCodingScheme & 0xf0) == 0xf0) { 1425 hasMessageClass = true; 1426 userDataCompressed = false; 1427 1428 if (0 == (mDataCodingScheme & 0x04)) { 1429 // GSM 7 bit default alphabet 1430 encodingType = ENCODING_7BIT; 1431 } else { 1432 // 8 bit data 1433 encodingType = ENCODING_8BIT; 1434 } 1435 } else if ((mDataCodingScheme & 0xF0) == 0xC0 1436 || (mDataCodingScheme & 0xF0) == 0xD0 1437 || (mDataCodingScheme & 0xF0) == 0xE0) { 1438 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 1439 1440 // 0xC0 == 7 bit, don't store 1441 // 0xD0 == 7 bit, store 1442 // 0xE0 == UCS-2, store 1443 1444 if ((mDataCodingScheme & 0xF0) == 0xE0) { 1445 encodingType = ENCODING_16BIT; 1446 } else { 1447 encodingType = ENCODING_7BIT; 1448 } 1449 1450 userDataCompressed = false; 1451 boolean active = ((mDataCodingScheme & 0x08) == 0x08); 1452 // bit 0x04 reserved 1453 1454 // VM - If TP-UDH is present, these values will be overwritten 1455 if ((mDataCodingScheme & 0x03) == 0x00) { 1456 mIsMwi = true; /* Indicates vmail */ 1457 mMwiSense = active;/* Indicates vmail notification set/clear */ 1458 mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0); 1459 1460 /* Set voice mail count based on notification bit */ 1461 if (active == true) { 1462 mVoiceMailCount = -1; // unknown number of messages waiting 1463 } else { 1464 mVoiceMailCount = 0; // no unread messages 1465 } 1466 1467 Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = " 1468 + (mDataCodingScheme & 0xff) + " Dont store = " 1469 + mMwiDontStore + " vmail count = " + mVoiceMailCount); 1470 1471 } else { 1472 mIsMwi = false; 1473 Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: " 1474 + (mDataCodingScheme & 0xff)); 1475 } 1476 } else if ((mDataCodingScheme & 0xC0) == 0x80) { 1477 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 1478 // 0x80..0xBF == Reserved coding groups 1479 if (mDataCodingScheme == 0x84) { 1480 // This value used for KSC5601 by carriers in Korea. 1481 encodingType = ENCODING_KSC5601; 1482 } else { 1483 Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme " 1484 + (mDataCodingScheme & 0xff)); 1485 } 1486 } else { 1487 Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme " 1488 + (mDataCodingScheme & 0xff)); 1489 } 1490 1491 // set both the user data and the user data header. 1492 int count = p.constructUserData(hasUserDataHeader, 1493 encodingType == ENCODING_7BIT); 1494 this.mUserData = p.getUserData(); 1495 this.mUserDataHeader = p.getUserDataHeader(); 1496 1497 /* 1498 * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24 1499 * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND) 1500 * ieidl =2 octets 1501 * ieda msg_ind_type = 0x00 (voice mail; discard sms )or 1502 * = 0x80 (voice mail; store sms) 1503 * msg_count = 0x00 ..0xFF 1504 */ 1505 if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) { 1506 for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) { 1507 int msgInd = msg.msgIndType & 0xff; 1508 /* 1509 * TS 23.040 V6.8.1 Sec 9.2.3.24.2 1510 * bits 1 0 : basic message indication type 1511 * bits 4 3 2 : extended message indication type 1512 * bits 6 5 : Profile id bit 7 storage type 1513 */ 1514 if ((msgInd == 0) || (msgInd == 0x80)) { 1515 mIsMwi = true; 1516 if (msgInd == 0x80) { 1517 /* Store message because TP_UDH indicates so*/ 1518 mMwiDontStore = false; 1519 } else if (mMwiDontStore == false) { 1520 /* Storage bit is not set by TP_UDH 1521 * Check for conflict 1522 * between message storage bit in TP_UDH 1523 * & DCS. The message shall be stored if either of 1524 * the one indicates so. 1525 * TS 23.040 V6.8.1 Sec 9.2.3.24.2 1526 */ 1527 if (!((((mDataCodingScheme & 0xF0) == 0xD0) 1528 || ((mDataCodingScheme & 0xF0) == 0xE0)) 1529 && ((mDataCodingScheme & 0x03) == 0x00))) { 1530 /* Even DCS did not have voice mail with Storage bit 1531 * 3GPP TS 23.038 V7.0.0 section 4 1532 * So clear this flag*/ 1533 mMwiDontStore = true; 1534 } 1535 } 1536 1537 mVoiceMailCount = msg.msgCount & 0xff; 1538 1539 /* 1540 * In the event of a conflict between message count setting 1541 * and DCS then the Message Count in the TP-UDH shall 1542 * override the indication in the TP-DCS. Set voice mail 1543 * notification based on count in TP-UDH 1544 */ 1545 if (mVoiceMailCount > 0) 1546 mMwiSense = true; 1547 else 1548 mMwiSense = false; 1549 1550 Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd 1551 + " Dont store = " + mMwiDontStore + " Vmail count = " 1552 + mVoiceMailCount); 1553 1554 /* 1555 * There can be only one IE for each type of message 1556 * indication in TP_UDH. In the event they are duplicated 1557 * last occurence will be used. Hence the for loop 1558 */ 1559 } else { 1560 Rlog.w(LOG_TAG, "TP_UDH fax/email/" 1561 + "extended msg/multisubscriber profile. Msg Ind = " + msgInd); 1562 } 1563 } // end of for 1564 } // end of if UDH 1565 1566 switch (encodingType) { 1567 case ENCODING_UNKNOWN: 1568 mMessageBody = null; 1569 break; 1570 1571 case ENCODING_8BIT: 1572 //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string 1573 //that's stored in 8-bit unpacked format) characters. 1574 if (r.getBoolean(com.android.internal. 1575 R.bool.config_sms_decode_gsm_8bit_data)) { 1576 mMessageBody = p.getUserDataGSM8bit(count); 1577 } else { 1578 mMessageBody = null; 1579 } 1580 break; 1581 1582 case ENCODING_7BIT: 1583 mMessageBody = p.getUserDataGSM7Bit(count, 1584 hasUserDataHeader ? mUserDataHeader.languageTable : 0, 1585 hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0); 1586 break; 1587 1588 case ENCODING_16BIT: 1589 mMessageBody = p.getUserDataUCS2(count); 1590 break; 1591 1592 case ENCODING_KSC5601: 1593 mMessageBody = p.getUserDataKSC5601(count); 1594 break; 1595 } 1596 1597 if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'"); 1598 1599 if (mMessageBody != null) { 1600 parseMessageBody(); 1601 } 1602 1603 if (!hasMessageClass) { 1604 messageClass = MessageClass.UNKNOWN; 1605 } else { 1606 switch (mDataCodingScheme & 0x3) { 1607 case 0: 1608 messageClass = MessageClass.CLASS_0; 1609 break; 1610 case 1: 1611 messageClass = MessageClass.CLASS_1; 1612 break; 1613 case 2: 1614 messageClass = MessageClass.CLASS_2; 1615 break; 1616 case 3: 1617 messageClass = MessageClass.CLASS_3; 1618 break; 1619 } 1620 } 1621 } 1622 1623 /** 1624 * {@inheritDoc} 1625 */ 1626 @Override getMessageClass()1627 public MessageClass getMessageClass() { 1628 return messageClass; 1629 } 1630 1631 /** 1632 * Returns true if this is a (U)SIM data download type SM. 1633 * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9. 1634 * 1635 * @return true if this is a USIM data download message; false otherwise 1636 */ isUsimDataDownload()1637 boolean isUsimDataDownload() { 1638 return messageClass == MessageClass.CLASS_2 && 1639 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c); 1640 } 1641 getNumOfVoicemails()1642 public int getNumOfVoicemails() { 1643 /* 1644 * Order of priority if multiple indications are present is 1.UDH, 1645 * 2.DCS, 3.CPHS. 1646 * Voice mail count if voice mail present indication is 1647 * received 1648 * 1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040] 1649 * 2. DCS only: count is unknown mVoiceMailCount= -1 1650 * 3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700] 1651 * Voice mail clear, mVoiceMailCount = 0. 1652 */ 1653 if ((!mIsMwi) && isCphsMwiMessage()) { 1654 if (mOriginatingAddress != null 1655 && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) { 1656 mVoiceMailCount = 0xff; 1657 } else { 1658 mVoiceMailCount = 0; 1659 } 1660 Rlog.v(LOG_TAG, "CPHS voice mail message"); 1661 } 1662 return mVoiceMailCount; 1663 } 1664 } 1665