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