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