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