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