1 /* 2 * Copyright (C) 2008 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.cdma; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 import android.sysprop.TelephonyProperties; 22 import android.telephony.PhoneNumberUtils; 23 import android.telephony.SmsCbLocation; 24 import android.telephony.SmsCbMessage; 25 import android.telephony.cdma.CdmaSmsCbProgramData; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 30 import com.android.internal.telephony.SmsAddress; 31 import com.android.internal.telephony.SmsConstants; 32 import com.android.internal.telephony.SmsHeader; 33 import com.android.internal.telephony.SmsMessageBase; 34 import com.android.internal.telephony.cdma.sms.BearerData; 35 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; 36 import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress; 37 import com.android.internal.telephony.cdma.sms.SmsEnvelope; 38 import com.android.internal.telephony.cdma.sms.UserData; 39 import com.android.internal.telephony.uicc.IccUtils; 40 import com.android.internal.util.BitwiseInputStream; 41 import com.android.internal.util.HexDump; 42 import com.android.telephony.Rlog; 43 44 import java.io.BufferedOutputStream; 45 import java.io.ByteArrayInputStream; 46 import java.io.ByteArrayOutputStream; 47 import java.io.DataInputStream; 48 import java.io.DataOutputStream; 49 import java.io.IOException; 50 import java.util.ArrayList; 51 52 /** 53 * TODO(cleanup): these constants are disturbing... are they not just 54 * different interpretations on one number? And if we did not have 55 * terrible class name overlap, they would not need to be directly 56 * imported like this. The class in this file could just as well be 57 * named CdmaSmsMessage, could it not? 58 */ 59 60 /** 61 * TODO(cleanup): internally returning null in many places makes 62 * debugging very hard (among many other reasons) and should be made 63 * more meaningful (replaced with exceptions for example). Null 64 * returns should only occur at the very outside of the module/class 65 * scope. 66 */ 67 68 /** 69 * A Short Message Service message. 70 * 71 */ 72 public class SmsMessage extends SmsMessageBase { 73 static final String LOG_TAG = "SmsMessage"; 74 static private final String LOGGABLE_TAG = "CDMA:SMS"; 75 private static final boolean VDBG = false; 76 77 private final static byte TELESERVICE_IDENTIFIER = 0x00; 78 private final static byte SERVICE_CATEGORY = 0x01; 79 private final static byte ORIGINATING_ADDRESS = 0x02; 80 private final static byte ORIGINATING_SUB_ADDRESS = 0x03; 81 private final static byte DESTINATION_ADDRESS = 0x04; 82 private final static byte DESTINATION_SUB_ADDRESS = 0x05; 83 private final static byte BEARER_REPLY_OPTION = 0x06; 84 private final static byte CAUSE_CODES = 0x07; 85 private final static byte BEARER_DATA = 0x08; 86 87 /** 88 * Status of a previously submitted SMS. 89 * This field applies to SMS Delivery Acknowledge messages. 0 indicates success; 90 * Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0. 91 * See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values. 92 */ 93 private int status; 94 95 /** Specifies if a return of an acknowledgment is requested for send SMS */ 96 private static final int RETURN_NO_ACK = 0; 97 private static final int RETURN_ACK = 1; 98 99 /** 100 * Supported priority modes for CDMA SMS messages 101 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) 102 */ 103 private static final int PRIORITY_NORMAL = 0x0; 104 private static final int PRIORITY_INTERACTIVE = 0x1; 105 private static final int PRIORITY_URGENT = 0x2; 106 private static final int PRIORITY_EMERGENCY = 0x3; 107 108 @UnsupportedAppUsage 109 private SmsEnvelope mEnvelope; 110 @UnsupportedAppUsage 111 private BearerData mBearerData; 112 113 /** @hide */ SmsMessage(SmsAddress addr, SmsEnvelope env)114 public SmsMessage(SmsAddress addr, SmsEnvelope env) { 115 mOriginatingAddress = addr; 116 mEnvelope = env; 117 createPdu(); 118 } 119 120 @UnsupportedAppUsage SmsMessage()121 public SmsMessage() {} 122 123 public static class SubmitPdu extends SubmitPduBase { 124 @UnsupportedAppUsage SubmitPdu()125 public SubmitPdu() { 126 } 127 } 128 129 /** 130 * Create an SmsMessage from a raw PDU. 131 * Note: In CDMA the PDU is just a byte representation of the received Sms. 132 */ 133 @UnsupportedAppUsage createFromPdu(byte[] pdu)134 public static SmsMessage createFromPdu(byte[] pdu) { 135 SmsMessage msg = new SmsMessage(); 136 137 try { 138 msg.parsePdu(pdu); 139 return msg; 140 } catch (RuntimeException ex) { 141 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 142 return null; 143 } catch (OutOfMemoryError e) { 144 Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e); 145 return null; 146 } 147 } 148 149 /** 150 * Creates an SmsMessage from an SMS EF record. 151 * 152 * @param index Index of SMS EF record. 153 * @param data Record data. 154 * @return An SmsMessage representing the record. 155 * 156 * @hide 157 */ 158 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " 159 + "android.telephony.SmsMessage} API instead") createFromEfRecord(int index, byte[] data)160 public static SmsMessage createFromEfRecord(int index, byte[] data) { 161 try { 162 SmsMessage msg = new SmsMessage(); 163 164 msg.mIndexOnIcc = index; 165 166 // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT, 167 // or STORED_UNSENT 168 // See 3GPP2 C.S0023 3.4.27 169 if ((data[0] & 1) == 0) { 170 Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record"); 171 return null; 172 } else { 173 msg.mStatusOnIcc = data[0] & 0x07; 174 } 175 176 // Second byte is the MSG_LEN, length of the message 177 // See 3GPP2 C.S0023 3.4.27 178 int size = data[1] & 0xFF; 179 180 // Note: Data may include trailing FF's. That's OK; message 181 // should still parse correctly. 182 byte[] pdu = new byte[size]; 183 System.arraycopy(data, 2, pdu, 0, size); 184 // the message has to be parsed before it can be displayed 185 // see gsm.SmsMessage 186 msg.parsePduFromEfRecord(pdu); 187 return msg; 188 } catch (RuntimeException ex) { 189 Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); 190 return null; 191 } 192 193 } 194 195 /** 196 * Note: This function is a GSM specific functionality which is not supported in CDMA mode. 197 */ getTPLayerLengthForPDU(String pdu)198 public static int getTPLayerLengthForPDU(String pdu) { 199 Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode."); 200 return 0; 201 } 202 203 /** 204 * Gets an SMS-SUBMIT PDU for a destination address and a message. 205 * 206 * @param scAddr Service Centre address. No use for this message. 207 * @param destAddr the address of the destination for the message. 208 * @param message string representation of the message payload. 209 * @param statusReportRequested indicates whether a report is requested for this message. 210 * @param smsHeader array containing the data for the User Data Header, preceded by the Element 211 * Identifiers. 212 * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns 213 * null on encode error. 214 * @hide 215 */ 216 @UnsupportedAppUsage getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader)217 public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message, 218 boolean statusReportRequested, SmsHeader smsHeader) { 219 return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1); 220 } 221 222 /** 223 * Gets an SMS-SUBMIT PDU for a destination address and a message. 224 * 225 * @param scAddr Service Centre address. No use for this message. 226 * @param destAddr the address of the destination for the message. 227 * @param message string representation of the message payload. 228 * @param statusReportRequested indicates whether a report is requested for this message. 229 * @param smsHeader array containing the data for the User Data Header, preceded by the Element 230 * Identifiers. 231 * @param priority priority level of the message. 232 * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns 233 * null on encode error. 234 * @hide 235 */ 236 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority)237 public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message, 238 boolean statusReportRequested, SmsHeader smsHeader, int priority) { 239 240 /** 241 * TODO(cleanup): Do we really want silent failure like this? 242 * Would it not be much more reasonable to make sure we don't 243 * call this function if we really want nothing done? 244 */ 245 if (message == null || destAddr == null) { 246 return null; 247 } 248 249 UserData uData = new UserData(); 250 uData.payloadStr = message; 251 uData.userDataHeader = smsHeader; 252 return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority); 253 } 254 255 /** 256 * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. 257 * 258 * @param scAddr Service Centre address. No use for this message. 259 * @param destAddr the address of the destination for the message. 260 * @param destPort the port to deliver the message to at the destination. 261 * @param data the data for the message. 262 * @param statusReportRequested indicates whether a report is requested for this message. 263 * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns 264 * null on encode error. 265 */ 266 @UnsupportedAppUsage getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] data, boolean statusReportRequested)267 public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort, 268 byte[] data, boolean statusReportRequested) { 269 270 /** 271 * TODO(cleanup): this is not a general-purpose SMS creation 272 * method, but rather something specialized to messages 273 * containing OCTET encoded (meaning non-human-readable) user 274 * data. The name should reflect that, and not just overload. 275 */ 276 277 SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs(); 278 portAddrs.destPort = destPort; 279 portAddrs.origPort = 0; 280 portAddrs.areEightBits = false; 281 282 SmsHeader smsHeader = new SmsHeader(); 283 smsHeader.portAddrs = portAddrs; 284 285 UserData uData = new UserData(); 286 uData.userDataHeader = smsHeader; 287 uData.msgEncoding = UserData.ENCODING_OCTET; 288 uData.msgEncodingSet = true; 289 uData.payload = data; 290 291 return privateGetSubmitPdu(destAddr, statusReportRequested, uData); 292 } 293 294 /** 295 * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. 296 * 297 * @param destAddr the address of the destination for the message. 298 * @param userData the data for the message. 299 * @param statusReportRequested indicates whether a report is requested for this message. 300 * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns 301 * null on encode error. 302 */ 303 @UnsupportedAppUsage getSubmitPdu(String destAddr, UserData userData, boolean statusReportRequested)304 public static SubmitPdu getSubmitPdu(String destAddr, UserData userData, 305 boolean statusReportRequested) { 306 return privateGetSubmitPdu(destAddr, statusReportRequested, userData); 307 } 308 309 /** 310 * Gets an SMS-SUBMIT PDU for a data message to a destination address & port. 311 * 312 * @param destAddr the address of the destination for the message. 313 * @param userData the data for the message. 314 * @param statusReportRequested indicates whether a report is requested for this message. 315 * @param priority Priority level of the message. 316 * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns 317 * null on encode error. 318 */ 319 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getSubmitPdu(String destAddr, UserData userData, boolean statusReportRequested, int priority)320 public static SubmitPdu getSubmitPdu(String destAddr, UserData userData, 321 boolean statusReportRequested, int priority) { 322 return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority); 323 } 324 325 /** 326 * Note: This function is a GSM specific functionality which is not supported in CDMA mode. 327 */ 328 @Override getProtocolIdentifier()329 public int getProtocolIdentifier() { 330 Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode."); 331 // (3GPP TS 23.040): "no interworking, but SME to SME protocol": 332 return 0; 333 } 334 335 /** 336 * Note: This function is a GSM specific functionality which is not supported in CDMA mode. 337 */ 338 @Override isReplace()339 public boolean isReplace() { 340 Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode."); 341 return false; 342 } 343 344 /** 345 * {@inheritDoc} 346 * Note: This function is a GSM specific functionality which is not supported in CDMA mode. 347 */ 348 @Override isCphsMwiMessage()349 public boolean isCphsMwiMessage() { 350 Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode."); 351 return false; 352 } 353 354 /** 355 * {@inheritDoc} 356 */ 357 @Override isMWIClearMessage()358 public boolean isMWIClearMessage() { 359 return ((mBearerData != null) && (mBearerData.numberOfMessages == 0)); 360 } 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override isMWISetMessage()366 public boolean isMWISetMessage() { 367 return ((mBearerData != null) && (mBearerData.numberOfMessages > 0)); 368 } 369 370 /** 371 * {@inheritDoc} 372 */ 373 @Override isMwiDontStore()374 public boolean isMwiDontStore() { 375 return ((mBearerData != null) && 376 (mBearerData.numberOfMessages > 0) && 377 (mBearerData.userData == null)); 378 } 379 380 /** 381 * Returns the status for a previously submitted message. 382 * For not interfering with status codes from GSM, this status code is 383 * shifted to the bits 31-16. 384 */ 385 @Override getStatus()386 public int getStatus() { 387 return (status << 16); 388 } 389 390 /** Return true iff the bearer data message type is DELIVERY_ACK. */ 391 @UnsupportedAppUsage 392 @Override isStatusReportMessage()393 public boolean isStatusReportMessage() { 394 return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK); 395 } 396 397 /** 398 * Note: This function is a GSM specific functionality which is not supported in CDMA mode. 399 */ 400 @Override isReplyPathPresent()401 public boolean isReplyPathPresent() { 402 Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode."); 403 return false; 404 } 405 406 /** 407 * Calculate the number of septets needed to encode the message. 408 * 409 * @param messageBody the message to encode 410 * @param use7bitOnly ignore (but still count) illegal characters if true 411 * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg 412 * @return TextEncodingDetails 413 */ 414 @UnsupportedAppUsage calculateLength(CharSequence messageBody, boolean use7bitOnly, boolean isEntireMsg)415 public static TextEncodingDetails calculateLength(CharSequence messageBody, 416 boolean use7bitOnly, boolean isEntireMsg) { 417 return BearerData.calcTextEncodingDetails(messageBody, use7bitOnly, isEntireMsg); 418 } 419 420 /** 421 * Returns the teleservice type of the message. 422 * @return the teleservice: 423 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET}, 424 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT}, 425 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT}, 426 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN}, 427 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP} 428 */ 429 @UnsupportedAppUsage getTeleService()430 public int getTeleService() { 431 return mEnvelope.teleService; 432 } 433 434 /** 435 * Returns the message type of the message. 436 * @return the message type: 437 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT}, 438 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST}, 439 * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE}, 440 */ 441 @UnsupportedAppUsage getMessageType()442 public int getMessageType() { 443 // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs. 444 // Use the service category parameter to detect CMAS and other cell broadcast messages. 445 if (mEnvelope.serviceCategory != 0) { 446 return SmsEnvelope.MESSAGE_TYPE_BROADCAST; 447 } else { 448 return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; 449 } 450 } 451 452 /** 453 * Decodes pdu to an empty SMS object. 454 * In the CDMA case the pdu is just an internal byte stream representation 455 * of the SMS Java-object. 456 * @see #createPdu() 457 */ parsePdu(byte[] pdu)458 private void parsePdu(byte[] pdu) { 459 ByteArrayInputStream bais = new ByteArrayInputStream(pdu); 460 DataInputStream dis = new DataInputStream(bais); 461 int length; 462 int bearerDataLength; 463 SmsEnvelope env = new SmsEnvelope(); 464 CdmaSmsAddress addr = new CdmaSmsAddress(); 465 // We currently do not parse subaddress in PDU, but it is required when determining 466 // fingerprint (see getIncomingSmsFingerprint()). 467 CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress(); 468 469 try { 470 env.messageType = dis.readInt(); 471 env.teleService = dis.readInt(); 472 env.serviceCategory = dis.readInt(); 473 474 addr.digitMode = dis.readByte(); 475 addr.numberMode = dis.readByte(); 476 addr.ton = dis.readByte(); 477 addr.numberPlan = dis.readByte(); 478 479 length = dis.readUnsignedByte(); 480 addr.numberOfDigits = length; 481 482 // Correctness check on the length 483 if (length > pdu.length) { 484 throw new RuntimeException( 485 "createFromPdu: Invalid pdu, addr.numberOfDigits " + length 486 + " > pdu len " + pdu.length); 487 } 488 addr.origBytes = new byte[length]; 489 dis.read(addr.origBytes, 0, length); // digits 490 491 env.bearerReply = dis.readInt(); 492 // CauseCode values: 493 env.replySeqNo = dis.readByte(); 494 env.errorClass = dis.readByte(); 495 env.causeCode = dis.readByte(); 496 497 //encoded BearerData: 498 bearerDataLength = dis.readInt(); 499 // Correctness check on the length 500 if (bearerDataLength > pdu.length) { 501 throw new RuntimeException( 502 "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength 503 + " > pdu len " + pdu.length); 504 } 505 env.bearerData = new byte[bearerDataLength]; 506 dis.read(env.bearerData, 0, bearerDataLength); 507 dis.close(); 508 } catch (IOException ex) { 509 throw new RuntimeException( 510 "createFromPdu: conversion from byte array to object failed: " + ex, ex); 511 } catch (Exception ex) { 512 Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex); 513 } 514 515 // link the filled objects to this SMS 516 mOriginatingAddress = addr; 517 env.origAddress = addr; 518 env.origSubaddress = subaddr; 519 mEnvelope = env; 520 mPdu = pdu; 521 522 parseSms(); 523 } 524 525 /** 526 * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0 527 */ parsePduFromEfRecord(byte[] pdu)528 private void parsePduFromEfRecord(byte[] pdu) { 529 ByteArrayInputStream bais = new ByteArrayInputStream(pdu); 530 DataInputStream dis = new DataInputStream(bais); 531 SmsEnvelope env = new SmsEnvelope(); 532 CdmaSmsAddress addr = new CdmaSmsAddress(); 533 CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress(); 534 535 try { 536 env.messageType = dis.readByte(); 537 538 while (dis.available() > 0) { 539 int parameterId = dis.readByte(); 540 int parameterLen = dis.readUnsignedByte(); 541 byte[] parameterData = new byte[parameterLen]; 542 543 switch (parameterId) { 544 case TELESERVICE_IDENTIFIER: 545 /* 546 * 16 bit parameter that identifies which upper layer 547 * service access point is sending or should receive 548 * this message 549 */ 550 env.teleService = dis.readUnsignedShort(); 551 Rlog.i(LOG_TAG, "teleservice = " + env.teleService); 552 break; 553 case SERVICE_CATEGORY: 554 /* 555 * 16 bit parameter that identifies type of service as 556 * in 3GPP2 C.S0015-0 Table 3.4.3.2-1 557 */ 558 env.serviceCategory = dis.readUnsignedShort(); 559 break; 560 case ORIGINATING_ADDRESS: 561 case DESTINATION_ADDRESS: 562 dis.read(parameterData, 0, parameterLen); 563 BitwiseInputStream addrBis = new BitwiseInputStream(parameterData); 564 addr.digitMode = addrBis.read(1); 565 addr.numberMode = addrBis.read(1); 566 int numberType = 0; 567 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 568 numberType = addrBis.read(3); 569 addr.ton = numberType; 570 571 if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) 572 addr.numberPlan = addrBis.read(4); 573 } 574 575 addr.numberOfDigits = addrBis.read(8); 576 577 byte[] data = new byte[addr.numberOfDigits]; 578 byte b = 0x00; 579 580 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) { 581 /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */ 582 for (int index = 0; index < addr.numberOfDigits; index++) { 583 b = (byte) (0xF & addrBis.read(4)); 584 // convert the value if it is 4-bit DTMF to 8 585 // bit 586 data[index] = convertDtmfToAscii(b); 587 } 588 } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 589 if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) { 590 for (int index = 0; index < addr.numberOfDigits; index++) { 591 b = (byte) (0xFF & addrBis.read(8)); 592 data[index] = b; 593 } 594 595 } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) { 596 if (numberType == 2) 597 Rlog.e(LOG_TAG, "TODO: Addr is email id"); 598 else 599 Rlog.e(LOG_TAG, 600 "TODO: Addr is data network address"); 601 } else { 602 Rlog.e(LOG_TAG, "Addr is of incorrect type"); 603 } 604 } else { 605 Rlog.e(LOG_TAG, "Incorrect Digit mode"); 606 } 607 addr.origBytes = data; 608 Rlog.pii(LOG_TAG, "Addr=" + addr.toString()); 609 if (parameterId == ORIGINATING_ADDRESS) { 610 env.origAddress = addr; 611 mOriginatingAddress = addr; 612 } else { 613 env.destAddress = addr; 614 mRecipientAddress = addr; 615 } 616 break; 617 case ORIGINATING_SUB_ADDRESS: 618 case DESTINATION_SUB_ADDRESS: 619 dis.read(parameterData, 0, parameterLen); 620 BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData); 621 subAddr.type = subAddrBis.read(3); 622 subAddr.odd = subAddrBis.readByteArray(1)[0]; 623 int subAddrLen = subAddrBis.read(8); 624 byte[] subdata = new byte[subAddrLen]; 625 for (int index = 0; index < subAddrLen; index++) { 626 b = (byte) (0xFF & subAddrBis.read(4)); 627 // convert the value if it is 4-bit DTMF to 8 bit 628 subdata[index] = convertDtmfToAscii(b); 629 } 630 subAddr.origBytes = subdata; 631 if (parameterId == ORIGINATING_SUB_ADDRESS) { 632 env.origSubaddress = subAddr; 633 } else { 634 env.destSubaddress = subAddr; 635 } 636 break; 637 case BEARER_REPLY_OPTION: 638 dis.read(parameterData, 0, parameterLen); 639 BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData); 640 env.bearerReply = replyOptBis.read(6); 641 break; 642 case CAUSE_CODES: 643 dis.read(parameterData, 0, parameterLen); 644 BitwiseInputStream ccBis = new BitwiseInputStream(parameterData); 645 env.replySeqNo = ccBis.readByteArray(6)[0]; 646 env.errorClass = ccBis.readByteArray(2)[0]; 647 if (env.errorClass != 0x00) 648 env.causeCode = ccBis.readByteArray(8)[0]; 649 break; 650 case BEARER_DATA: 651 dis.read(parameterData, 0, parameterLen); 652 env.bearerData = parameterData; 653 break; 654 default: 655 throw new Exception("unsupported parameterId (" + parameterId + ")"); 656 } 657 } 658 bais.close(); 659 dis.close(); 660 } catch (Exception ex) { 661 Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex); 662 } 663 664 // link the filled objects to this SMS 665 mEnvelope = env; 666 mPdu = pdu; 667 668 parseSms(); 669 } 670 671 /** 672 * Pre-processes an SMS WAP for Teleservice Id 0xFDEA(65002). 673 * 674 * It requires an additional header parsing to extract new Message Identifier and new User Data 675 * from WDP SMS User Data. 676 * 677 * - WDP SMS User Data Subparameter = 678 * |User Data SUBPARAMETER_ID ~ NUM_FIELDS| + |CHARi| + |RESERVED| 679 * 680 * - WDP SMS User Data Subparameter CHARi = 681 * |New Message Identifier Subparameter(HEADER_IND = 0)| + 682 * |New User Data Subparameter(MSG_ENCODING = ENCODING_OCTET)| 683 * 684 * @return true if preprocessing is successful, false otherwise. 685 */ preprocessCdmaFdeaWap()686 public boolean preprocessCdmaFdeaWap() { 687 try { 688 BitwiseInputStream inStream = new BitwiseInputStream(mUserData); 689 690 // Message Identifier SUBPARAMETER_ID(0x00) 691 if (inStream.read(8) != 0x00) { 692 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAMETER_ID"); 693 return false; 694 } 695 696 // Message Identifier SUBPARAM_LEN(0x03) 697 if (inStream.read(8) != 0x03) { 698 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAM_LEN"); 699 return false; 700 } 701 702 // Message Identifier MESSAGE_TYPE 703 mBearerData.messageType = inStream.read(4); 704 705 // Message Identifier MESSAGE_ID 706 int msgId = inStream.read(8) << 8; 707 msgId |= inStream.read(8); 708 mBearerData.messageId = msgId; 709 mMessageRef = msgId; 710 711 // Message Identifier HEADER_IND 712 mBearerData.hasUserDataHeader = (inStream.read(1) == 1); 713 if (mBearerData.hasUserDataHeader) { 714 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier HEADER_IND"); 715 return false; 716 } 717 718 // Message Identifier RESERVED 719 inStream.skip(3); 720 721 // User Data SUBPARAMETER_ID(0x01) 722 if (inStream.read(8) != 0x01) { 723 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data SUBPARAMETER_ID"); 724 return false; 725 } 726 727 // User Data SUBPARAM_LEN 728 int userDataLen = inStream.read(8) * 8; 729 730 // User Data MSG_ENCODING 731 mBearerData.userData.msgEncoding = inStream.read(5); 732 int consumedBits = 5; 733 if (mBearerData.userData.msgEncoding != UserData.ENCODING_OCTET) { 734 Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data MSG_ENCODING"); 735 return false; 736 } 737 738 // User Data NUM_FIELDS 739 mBearerData.userData.numFields = inStream.read(8); 740 consumedBits += 8; 741 742 int remainingBits = userDataLen - consumedBits; 743 int dataBits = mBearerData.userData.numFields * 8; 744 dataBits = dataBits < remainingBits ? dataBits : remainingBits; 745 mBearerData.userData.payload = inStream.readByteArray(dataBits); 746 mUserData = mBearerData.userData.payload; 747 return true; 748 } catch (BitwiseInputStream.AccessException ex) { 749 Rlog.e(LOG_TAG, "Fail to preprocess FDEA WAP: " + ex); 750 } 751 return false; 752 } 753 754 /** 755 * Parses a SMS message from its BearerData stream. 756 */ 757 @UnsupportedAppUsage 758 public void parseSms() { 759 // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6 760 // It contains only an 8-bit number with the number of messages waiting 761 if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) { 762 mBearerData = new BearerData(); 763 if (mEnvelope.bearerData != null) { 764 mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0]; 765 } 766 if (VDBG) { 767 Rlog.d(LOG_TAG, "parseSms: get MWI " + 768 Integer.toString(mBearerData.numberOfMessages)); 769 } 770 return; 771 } 772 mBearerData = BearerData.decode(mEnvelope.bearerData); 773 if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { 774 Rlog.d(LOG_TAG, "MT raw BearerData = '" + 775 HexDump.toHexString(mEnvelope.bearerData) + "'"); 776 Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData); 777 } 778 mMessageRef = mBearerData.messageId; 779 if (mBearerData.userData != null) { 780 mUserData = mBearerData.userData.payload; 781 mUserDataHeader = mBearerData.userData.userDataHeader; 782 mMessageBody = mBearerData.userData.payloadStr; 783 mReceivedEncodingType = mBearerData.userData.msgEncoding; 784 } 785 786 if (mOriginatingAddress != null) { 787 decodeSmsDisplayAddress(mOriginatingAddress); 788 if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: " + mOriginatingAddress.address); 789 } 790 791 if (mRecipientAddress != null) { 792 decodeSmsDisplayAddress(mRecipientAddress); 793 if (VDBG) Rlog.v(LOG_TAG, "SMS destination address: " + mRecipientAddress.address); 794 } 795 796 if (mBearerData.msgCenterTimeStamp != null) { 797 mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(); 798 } 799 800 if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis); 801 802 // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1) 803 if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) { 804 // The BearerData MsgStatus subparameter should only be 805 // included for DELIVERY_ACK messages. If it occurred for 806 // other messages, it would be unclear what the status 807 // being reported refers to. The MsgStatus subparameter 808 // is primarily useful to indicate error conditions -- a 809 // message without this subparameter is assumed to 810 // indicate successful delivery. 811 if (!mBearerData.messageStatusSet) { 812 Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" + 813 (mUserData == null ? "also missing" : "does have") + 814 " userData)."); 815 status = (BearerData.ERROR_NONE << 8) | BearerData.STATUS_DELIVERED; 816 } else { 817 status = mBearerData.errorClass << 8; 818 status |= mBearerData.messageStatus; 819 } 820 } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER 821 && mBearerData.messageType != BearerData.MESSAGE_TYPE_SUBMIT) { 822 throw new RuntimeException("Unsupported message type: " + mBearerData.messageType); 823 } 824 825 if (mMessageBody != null) { 826 if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'"); 827 parseMessageBody(); 828 } else if ((mUserData != null) && VDBG) { 829 Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'"); 830 } 831 } 832 833 private void decodeSmsDisplayAddress(SmsAddress addr) { 834 // PCD(Plus Code Dialing) 835 // 1) Replaces IDD(International Direct Dialing) with the '+' if address starts with it. 836 // TODO: Skip it for EF SMS(SUBMIT and DELIVER) because the IDD depends on current network? 837 // 2) Adds the '+' prefix if TON is International 838 // 3) Keeps the '+' if address starts with the '+' 839 String idd = TelephonyProperties.operator_idp_string().orElse(null); 840 addr.address = new String(addr.origBytes); 841 if (!TextUtils.isEmpty(idd) && addr.address.startsWith(idd)) { 842 addr.address = "+" + addr.address.substring(idd.length()); 843 } else if (addr.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) { 844 if (addr.address.charAt(0) != '+') { 845 addr.address = "+" + addr.address; 846 } 847 } 848 Rlog.pii(LOG_TAG, " decodeSmsDisplayAddress = " + addr.address); 849 } 850 851 /** 852 * Parses a broadcast SMS, possibly containing a CMAS alert. 853 * 854 * @param plmn the PLMN for a broadcast SMS 855 * @param slotIndex SIM slot index 856 * @param subId Subscription id 857 */ 858 public SmsCbMessage parseBroadcastSms(String plmn, int slotIndex, int subId) { 859 BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory); 860 if (bData == null) { 861 Rlog.w(LOG_TAG, "BearerData.decode() returned null"); 862 return null; 863 } 864 if (bData.userData != null) { 865 mReceivedEncodingType = bData.userData.msgEncoding; 866 } 867 868 if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { 869 Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData)); 870 } 871 872 SmsCbLocation location = new SmsCbLocation(plmn); 873 874 return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2, 875 SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location, 876 mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr, 877 bData.priority, null, bData.cmasWarningInfo, slotIndex, subId); 878 } 879 880 /** 881 * @return the bearer data byte array 882 */ 883 public byte[] getEnvelopeBearerData() { 884 return mEnvelope.bearerData; 885 } 886 887 /** 888 * @return the 16-bit CDMA SCPT service category 889 */ 890 public @CdmaSmsCbProgramData.Category int getEnvelopeServiceCategory() { 891 return mEnvelope.serviceCategory; 892 } 893 894 /** 895 * {@inheritDoc} 896 */ 897 @Override 898 public SmsConstants.MessageClass getMessageClass() { 899 if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) { 900 return SmsConstants.MessageClass.CLASS_0; 901 } else { 902 return SmsConstants.MessageClass.UNKNOWN; 903 } 904 } 905 906 /** 907 * Calculate the next message id, starting at 1 and iteratively 908 * incrementing within the range 1..65535 remembering the state 909 * via a persistent system property. (See C.S0015-B, v2.0, 910 * 4.3.1.5) Since this routine is expected to be accessed via via 911 * binder-call, and hence should be thread-safe, it has been 912 * synchronized. 913 */ 914 @UnsupportedAppUsage 915 public synchronized static int getNextMessageId() { 916 // Testing and dialog with partners has indicated that 917 // msgId==0 is (sometimes?) treated specially by lower levels. 918 // Specifically, the ID is not preserved for delivery ACKs. 919 // Hence, avoid 0 -- constraining the range to 1..65535. 920 int msgId = TelephonyProperties.cdma_msg_id().orElse(1); 921 int nextMsgId = msgId % 0xFFFF + 1; 922 try{ 923 TelephonyProperties.cdma_msg_id(nextMsgId); 924 if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { 925 Rlog.d(LOG_TAG, "next persist.radio.cdma.msgid = " + nextMsgId); 926 Rlog.d(LOG_TAG, "readback gets " + TelephonyProperties.cdma_msg_id().orElse(1)); 927 } 928 } catch(RuntimeException ex) { 929 Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex); 930 } 931 return msgId; 932 } 933 934 /** 935 * Creates BearerData and Envelope from parameters for a Submit SMS. 936 * @return byte stream for SubmitPdu. 937 */ 938 @UnsupportedAppUsage 939 private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, 940 UserData userData) { 941 return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1); 942 } 943 944 /** 945 * Creates BearerData and Envelope from parameters for a Submit SMS. 946 * @return byte stream for SubmitPdu. 947 */ 948 private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, 949 UserData userData, int priority) { 950 951 /** 952 * TODO(cleanup): give this function a more meaningful name. 953 */ 954 955 /** 956 * TODO(cleanup): Make returning null from the getSubmitPdu 957 * variations meaningful -- clean up the error feedback 958 * mechanism, and avoid null pointer exceptions. 959 */ 960 961 /** 962 * North America Plus Code : 963 * Convert + code to 011 and dial out for international SMS 964 */ 965 CdmaSmsAddress destAddr = CdmaSmsAddress.parse( 966 PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr)); 967 if (destAddr == null) return null; 968 969 BearerData bearerData = new BearerData(); 970 bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT; 971 972 bearerData.messageId = getNextMessageId(); 973 974 bearerData.deliveryAckReq = statusReportRequested; 975 bearerData.userAckReq = false; 976 bearerData.readAckReq = false; 977 bearerData.reportReq = false; 978 if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) { 979 bearerData.priorityIndicatorSet = true; 980 bearerData.priority = priority; 981 } 982 983 bearerData.userData = userData; 984 985 byte[] encodedBearerData = BearerData.encode(bearerData); 986 if (encodedBearerData == null) return null; 987 if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { 988 Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData); 989 Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'"); 990 } 991 992 int teleservice = (bearerData.hasUserDataHeader 993 && userData.msgEncoding != UserData.ENCODING_7BIT_ASCII) 994 ? SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; 995 996 SmsEnvelope envelope = new SmsEnvelope(); 997 envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; 998 envelope.teleService = teleservice; 999 envelope.destAddress = destAddr; 1000 envelope.bearerReply = RETURN_ACK; 1001 envelope.bearerData = encodedBearerData; 1002 1003 /** 1004 * TODO(cleanup): envelope looks to be a pointless class, get 1005 * rid of it. Also -- most of the envelope fields set here 1006 * are ignored, why? 1007 */ 1008 1009 try { 1010 /** 1011 * TODO(cleanup): reference a spec and get rid of the ugly comments 1012 */ 1013 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 1014 DataOutputStream dos = new DataOutputStream(baos); 1015 dos.writeInt(envelope.teleService); 1016 dos.writeInt(0); //servicePresent 1017 dos.writeInt(0); //serviceCategory 1018 dos.write(destAddr.digitMode); 1019 dos.write(destAddr.numberMode); 1020 dos.write(destAddr.ton); // number_type 1021 dos.write(destAddr.numberPlan); 1022 dos.write(destAddr.numberOfDigits); 1023 dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits 1024 // Subaddress is not supported. 1025 dos.write(0); //subaddressType 1026 dos.write(0); //subaddr_odd 1027 dos.write(0); //subaddr_nbr_of_digits 1028 dos.write(encodedBearerData.length); 1029 dos.write(encodedBearerData, 0, encodedBearerData.length); 1030 dos.close(); 1031 1032 SubmitPdu pdu = new SubmitPdu(); 1033 pdu.encodedMessage = baos.toByteArray(); 1034 pdu.encodedScAddress = null; 1035 return pdu; 1036 } catch(IOException ex) { 1037 Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex); 1038 } 1039 return null; 1040 } 1041 1042 /** 1043 * Gets an SMS-DELIVER PDU for a originating address and a message. 1044 * 1045 * @param origAddr the address of the originating for the message. 1046 * @param message string representation of the message payload. 1047 * @param date the time stamp the message was received. 1048 * @return a <code>SubmitPdu</code> containing null SC address and the encoded message. Returns 1049 * null on encode error. 1050 * @hide 1051 */ 1052 public static SubmitPdu getDeliverPdu(String origAddr, String message, long date) { 1053 if (origAddr == null || message == null) { 1054 return null; 1055 } 1056 1057 CdmaSmsAddress addr = CdmaSmsAddress.parse(origAddr); 1058 if (addr == null) return null; 1059 1060 BearerData bearerData = new BearerData(); 1061 bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; 1062 1063 bearerData.messageId = 0; 1064 1065 bearerData.deliveryAckReq = false; 1066 bearerData.userAckReq = false; 1067 bearerData.readAckReq = false; 1068 bearerData.reportReq = false; 1069 1070 bearerData.userData = new UserData(); 1071 bearerData.userData.payloadStr = message; 1072 1073 bearerData.msgCenterTimeStamp = BearerData.TimeStamp.fromMillis(date); 1074 1075 byte[] encodedBearerData = BearerData.encode(bearerData); 1076 if (encodedBearerData == null) return null; 1077 1078 try { 1079 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 1080 DataOutputStream dos = new DataOutputStream(baos); 1081 dos.writeInt(SmsEnvelope.TELESERVICE_WMT); 1082 dos.writeInt(0); // servicePresent 1083 dos.writeInt(0); // serviceCategory 1084 dos.write(addr.digitMode); 1085 dos.write(addr.numberMode); 1086 dos.write(addr.ton); // number_type 1087 dos.write(addr.numberPlan); 1088 dos.write(addr.numberOfDigits); 1089 dos.write(addr.origBytes, 0, addr.origBytes.length); // digits 1090 // Subaddress is not supported. 1091 dos.write(0); // subaddressType 1092 dos.write(0); // subaddr_odd 1093 dos.write(0); // subaddr_nbr_of_digits 1094 dos.write(encodedBearerData.length); 1095 dos.write(encodedBearerData, 0, encodedBearerData.length); 1096 dos.close(); 1097 1098 SubmitPdu pdu = new SubmitPdu(); 1099 pdu.encodedMessage = baos.toByteArray(); 1100 pdu.encodedScAddress = null; 1101 return pdu; 1102 } catch (IOException ex) { 1103 Rlog.e(LOG_TAG, "creating Deliver PDU failed: " + ex); 1104 } 1105 return null; 1106 } 1107 1108 /** 1109 * Creates byte array (pseudo pdu) from SMS object. 1110 * Note: Do not call this method more than once per object! 1111 * @hide 1112 */ 1113 public void createPdu() { 1114 SmsEnvelope env = mEnvelope; 1115 CdmaSmsAddress addr = env.origAddress; 1116 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 1117 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos)); 1118 1119 try { 1120 dos.writeInt(env.messageType); 1121 dos.writeInt(env.teleService); 1122 dos.writeInt(env.serviceCategory); 1123 1124 dos.writeByte(addr.digitMode); 1125 dos.writeByte(addr.numberMode); 1126 dos.writeByte(addr.ton); 1127 dos.writeByte(addr.numberPlan); 1128 dos.writeByte(addr.numberOfDigits); 1129 dos.write(addr.origBytes, 0, addr.origBytes.length); // digits 1130 1131 dos.writeInt(env.bearerReply); 1132 // CauseCode values: 1133 dos.writeByte(env.replySeqNo); 1134 dos.writeByte(env.errorClass); 1135 dos.writeByte(env.causeCode); 1136 //encoded BearerData: 1137 dos.writeInt(env.bearerData.length); 1138 dos.write(env.bearerData, 0, env.bearerData.length); 1139 dos.close(); 1140 1141 /** 1142 * TODO(cleanup) -- The mPdu field is managed in 1143 * a fragile manner, and it would be much nicer if 1144 * accessing the serialized representation used a less 1145 * fragile mechanism. Maybe the getPdu method could 1146 * generate a representation if there was not yet one? 1147 */ 1148 1149 mPdu = baos.toByteArray(); 1150 } catch (IOException ex) { 1151 Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex); 1152 } 1153 } 1154 1155 /** 1156 * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character 1157 * @hide 1158 */ 1159 public static byte convertDtmfToAscii(byte dtmfDigit) { 1160 byte asciiDigit; 1161 1162 switch (dtmfDigit) { 1163 case 0: asciiDigit = 68; break; // 'D' 1164 case 1: asciiDigit = 49; break; // '1' 1165 case 2: asciiDigit = 50; break; // '2' 1166 case 3: asciiDigit = 51; break; // '3' 1167 case 4: asciiDigit = 52; break; // '4' 1168 case 5: asciiDigit = 53; break; // '5' 1169 case 6: asciiDigit = 54; break; // '6' 1170 case 7: asciiDigit = 55; break; // '7' 1171 case 8: asciiDigit = 56; break; // '8' 1172 case 9: asciiDigit = 57; break; // '9' 1173 case 10: asciiDigit = 48; break; // '0' 1174 case 11: asciiDigit = 42; break; // '*' 1175 case 12: asciiDigit = 35; break; // '#' 1176 case 13: asciiDigit = 65; break; // 'A' 1177 case 14: asciiDigit = 66; break; // 'B' 1178 case 15: asciiDigit = 67; break; // 'C' 1179 default: 1180 asciiDigit = 32; // Invalid DTMF code 1181 break; 1182 } 1183 1184 return asciiDigit; 1185 } 1186 1187 /** This function shall be called to get the number of voicemails. 1188 * @hide 1189 */ 1190 @UnsupportedAppUsage 1191 public int getNumOfVoicemails() { 1192 return mBearerData.numberOfMessages; 1193 } 1194 1195 /** 1196 * Returns a byte array that can be use to uniquely identify a received SMS message. 1197 * C.S0015-B 4.3.1.6 Unique Message Identification. 1198 * 1199 * @return byte array uniquely identifying the message. 1200 * @hide 1201 */ 1202 @UnsupportedAppUsage 1203 public byte[] getIncomingSmsFingerprint() { 1204 ByteArrayOutputStream output = new ByteArrayOutputStream(); 1205 1206 output.write(mEnvelope.serviceCategory); 1207 output.write(mEnvelope.teleService); 1208 output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length); 1209 output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length); 1210 // subaddress is not set when parsing some MT SMS. 1211 if (mEnvelope.origSubaddress != null && mEnvelope.origSubaddress.origBytes != null) { 1212 output.write(mEnvelope.origSubaddress.origBytes, 0, 1213 mEnvelope.origSubaddress.origBytes.length); 1214 } 1215 1216 return output.toByteArray(); 1217 } 1218 1219 /** 1220 * Returns the list of service category program data, if present. 1221 * @return a list of CdmaSmsCbProgramData objects, or null if not present 1222 * @hide 1223 */ 1224 public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() { 1225 return mBearerData.serviceCategoryProgramData; 1226 } 1227 } 1228