1 /* 2 * Copyright (C) 2013 Samsung System LSI 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.android.bluetooth.map; 16 17 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; 18 19 import android.telephony.PhoneNumberUtils; 20 import android.telephony.SmsMessage; 21 import android.telephony.TelephonyManager; 22 import android.util.Log; 23 24 import com.android.internal.telephony.GsmAlphabet; 25 import com.android.internal.telephony.SmsConstants; 26 import com.android.internal.telephony.SmsHeader; 27 import com.android.internal.telephony.cdma.sms.BearerData; 28 import com.android.internal.telephony.cdma.sms.UserData; 29 30 import java.io.ByteArrayInputStream; 31 import java.io.ByteArrayOutputStream; 32 import java.io.IOException; 33 import java.io.UnsupportedEncodingException; 34 import java.text.SimpleDateFormat; 35 import java.util.ArrayList; 36 import java.util.Calendar; 37 import java.util.Date; 38 import java.util.Random; 39 40 public class BluetoothMapSmsPdu { 41 42 private static final String TAG = "BluetoothMapSmsPdu"; 43 private static final boolean V = false; 44 private static final int INVALID_VALUE = -1; 45 public static final int SMS_TYPE_GSM = 1; 46 public static final int SMS_TYPE_CDMA = 2; 47 48 49 /* We need to handle the SC-address mentioned in errata 4335. 50 * Since the definition could be read in three different ways, I have asked 51 * the car working group for clarification, and are awaiting confirmation that 52 * this clarification will go into the MAP spec: 53 * "The native format should be <sc_addr><tpdu> where <sc_addr> is <length><ton><1..10 octet 54 * of address> 55 * coded according to 24.011. The IEI is not to be used, as the fixed order of the data 56 * makes a type 4 LV 57 * information element sufficient. <length> is a single octet which value is the length of 58 * the value-field 59 * in octets including both the <ton> and the <address>." 60 * */ 61 62 63 public static class SmsPdu { 64 private byte[] mData; 65 private byte[] mScAddress = {0}; 66 // At the moment we do not use the scAddress, hence set the length to 0. 67 private int mUserDataMsgOffset = 0; 68 private int mEncoding; 69 private int mLanguageTable; 70 private int mLanguageShiftTable; 71 private int mType; 72 73 /* Members used for pdu decoding */ 74 private int mUserDataSeptetPadding = INVALID_VALUE; 75 private int mMsgSeptetCount = 0; 76 77 SmsPdu(byte[] data, int type) { 78 this.mData = data; 79 this.mEncoding = INVALID_VALUE; 80 this.mType = type; 81 this.mLanguageTable = INVALID_VALUE; 82 this.mLanguageShiftTable = INVALID_VALUE; 83 this.mUserDataMsgOffset = gsmSubmitGetTpUdOffset(); // Assume no user data header 84 } 85 86 /** 87 * Create a pdu instance based on the data generated on this device. 88 * @param data 89 * @param encoding 90 * @param type 91 * @param languageTable 92 */ 93 SmsPdu(byte[] data, int encoding, int type, int languageTable) { 94 this.mData = data; 95 this.mEncoding = encoding; 96 this.mType = type; 97 this.mLanguageTable = languageTable; 98 } 99 100 public byte[] getData() { 101 return mData; 102 } 103 104 public byte[] getScAddress() { 105 return mScAddress; 106 } 107 108 public void setEncoding(int encoding) { 109 this.mEncoding = encoding; 110 } 111 112 public int getEncoding() { 113 return mEncoding; 114 } 115 116 public int getType() { 117 return mType; 118 } 119 120 public int getUserDataMsgOffset() { 121 return mUserDataMsgOffset; 122 } 123 124 /** The user data message payload size in bytes - excluding the user data header. */ 125 public int getUserDataMsgSize() { 126 return mData.length - mUserDataMsgOffset; 127 } 128 129 public int getLanguageShiftTable() { 130 return mLanguageShiftTable; 131 } 132 133 public int getLanguageTable() { 134 return mLanguageTable; 135 } 136 137 public int getUserDataSeptetPadding() { 138 return mUserDataSeptetPadding; 139 } 140 141 public int getMsgSeptetCount() { 142 return mMsgSeptetCount; 143 } 144 145 146 /* PDU parsing/modification functionality */ 147 private static final byte TELESERVICE_IDENTIFIER = 0x00; 148 private static final byte SERVICE_CATEGORY = 0x01; 149 private static final byte ORIGINATING_ADDRESS = 0x02; 150 private static final byte ORIGINATING_SUB_ADDRESS = 0x03; 151 private static final byte DESTINATION_ADDRESS = 0x04; 152 private static final byte DESTINATION_SUB_ADDRESS = 0x05; 153 private static final byte BEARER_REPLY_OPTION = 0x06; 154 private static final byte CAUSE_CODES = 0x07; 155 private static final byte BEARER_DATA = 0x08; 156 157 /** 158 * Find and return the offset to the specified parameter ID 159 * @param parameterId The parameter ID to find 160 * @return the offset in number of bytes to the parameterID entry in the pdu data. 161 * The byte at the offset contains the parameter ID, the byte following contains the 162 * parameter length, and offset + 2 is the first byte of the parameter data. 163 */ 164 private int cdmaGetParameterOffset(byte parameterId) { 165 ByteArrayInputStream pdu = new ByteArrayInputStream(mData); 166 int offset = 0; 167 boolean found = false; 168 169 try { 170 pdu.skip(1); // Skip the message type 171 172 while (pdu.available() > 0) { 173 int currentId = pdu.read(); 174 int currentLen = pdu.read(); 175 176 if (currentId == parameterId) { 177 found = true; 178 break; 179 } else { 180 pdu.skip(currentLen); 181 offset += 2 + currentLen; 182 } 183 } 184 pdu.close(); 185 } catch (Exception e) { 186 Log.e(TAG, "cdmaGetParameterOffset: ", e); 187 } 188 189 if (found) { 190 return offset; 191 } else { 192 return 0; 193 } 194 } 195 196 private static final byte BEARER_DATA_MSG_ID = 0x00; 197 198 private int cdmaGetSubParameterOffset(byte subParameterId) { 199 ByteArrayInputStream pdu = new ByteArrayInputStream(mData); 200 int offset = 0; 201 boolean found = false; 202 offset = cdmaGetParameterOffset(BEARER_DATA) 203 + 2; // Add to offset the BEARER_DATA parameter id and length bytes 204 pdu.skip(offset); 205 try { 206 207 while (pdu.available() > 0) { 208 int currentId = pdu.read(); 209 int currentLen = pdu.read(); 210 211 if (currentId == subParameterId) { 212 found = true; 213 break; 214 } else { 215 pdu.skip(currentLen); 216 offset += 2 + currentLen; 217 } 218 } 219 pdu.close(); 220 } catch (Exception e) { 221 Log.e(TAG, "cdmaGetParameterOffset: ", e); 222 } 223 224 if (found) { 225 return offset; 226 } else { 227 return 0; 228 } 229 } 230 231 232 public void cdmaChangeToDeliverPdu(long date) { 233 /* Things to change: 234 * - Message Type in bearer data (Not the overall point-to-point type) 235 * - Change address ID from destination to originating (sub addresses are not used) 236 * - A time stamp is not mandatory. 237 */ 238 int offset; 239 if (mData == null) { 240 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 241 } 242 offset = cdmaGetParameterOffset(DESTINATION_ADDRESS); 243 if (mData.length < offset) { 244 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 245 } 246 mData[offset] = ORIGINATING_ADDRESS; 247 248 offset = cdmaGetParameterOffset(DESTINATION_SUB_ADDRESS); 249 if (mData.length < offset) { 250 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 251 } 252 mData[offset] = ORIGINATING_SUB_ADDRESS; 253 254 offset = cdmaGetSubParameterOffset(BEARER_DATA_MSG_ID); 255 256 if (mData.length > (2 + offset)) { 257 int tmp = mData[offset + 2] 258 & 0xff; // Skip the subParam ID and length, and read the first byte. 259 // Mask out the type 260 tmp &= 0x0f; 261 // Set the new type 262 tmp |= ((BearerData.MESSAGE_TYPE_DELIVER << 4) & 0xf0); 263 // Store the result 264 mData[offset + 2] = (byte) tmp; 265 266 } else { 267 throw new IllegalArgumentException("Unable to convert PDU to Deliver type"); 268 } 269 /* TODO: Do we need to change anything in the user data? Not sure if the user 270 data is 271 * just encoded using GSM encoding, or it is an actual GSM submit PDU 272 * embedded 273 * in the user data? 274 */ 275 } 276 277 private static final byte TP_MIT_DELIVER = 0x00; // bit 0 and 1 278 private static final byte TP_MMS_NO_MORE = 0x04; // bit 2 279 private static final byte TP_RP_NO_REPLY_PATH = 0x00; // bit 7 280 private static final byte TP_UDHI_MASK = 0x40; // bit 6 281 private static final byte TP_SRI_NO_REPORT = 0x00; // bit 5 282 283 private int gsmSubmitGetTpPidOffset() { 284 /* calculate the offset to TP_PID. 285 * The TP-DA has variable length, and the length excludes the 2 byte length and type 286 * headers. 287 * The TP-DA is two bytes within the PDU */ 288 // data[2] is the number of semi-octets in the phone number (ceil result) 289 int offset = 2 + ((mData[2] + 1) & 0xff) / 2 + 2; 290 // max length of TP_DA is 12 bytes + two byte offset. 291 if ((offset > mData.length) || (offset > (2 + 12))) { 292 throw new IllegalArgumentException( 293 "wrongly formatted gsm submit PDU. offset = " + offset); 294 } 295 return offset; 296 } 297 298 public int gsmSubmitGetTpDcs() { 299 return mData[gsmSubmitGetTpDcsOffset()] & 0xff; 300 } 301 302 public boolean gsmSubmitHasUserDataHeader() { 303 return ((mData[0] & 0xff) & TP_UDHI_MASK) == TP_UDHI_MASK; 304 } 305 306 private int gsmSubmitGetTpDcsOffset() { 307 return gsmSubmitGetTpPidOffset() + 1; 308 } 309 310 private int gsmSubmitGetTpUdlOffset() { 311 switch (((mData[0] & 0xff) & (0x08 | 0x04)) >> 2) { 312 case 0: // Not TP-VP present 313 return gsmSubmitGetTpPidOffset() + 2; 314 case 1: // TP-VP relative format 315 return gsmSubmitGetTpPidOffset() + 2 + 1; 316 case 2: // TP-VP enhanced format 317 case 3: // TP-VP absolute format 318 break; 319 } 320 return gsmSubmitGetTpPidOffset() + 2 + 7; 321 } 322 323 private int gsmSubmitGetTpUdOffset() { 324 return gsmSubmitGetTpUdlOffset() + 1; 325 } 326 327 public void gsmDecodeUserDataHeader() { 328 ByteArrayInputStream pdu = new ByteArrayInputStream(mData); 329 330 pdu.skip(gsmSubmitGetTpUdlOffset()); 331 int userDataLength = pdu.read(); 332 if (gsmSubmitHasUserDataHeader()) { 333 int userDataHeaderLength = pdu.read(); 334 335 // This part is only needed to extract the language info, hence only needed for 7 336 // bit encoding 337 if (mEncoding == SmsConstants.ENCODING_7BIT) { 338 byte[] udh = new byte[userDataHeaderLength]; 339 try { 340 pdu.read(udh); 341 } catch (IOException e) { 342 Log.w(TAG, "unable to read userDataHeader", e); 343 } 344 SmsHeader userDataHeader = SmsHeader.fromByteArray(udh); 345 mLanguageTable = userDataHeader.languageTable; 346 mLanguageShiftTable = userDataHeader.languageShiftTable; 347 348 int headerBits = (userDataHeaderLength + 1) * 8; 349 int headerSeptets = headerBits / 7; 350 headerSeptets += (headerBits % 7) > 0 ? 1 : 0; 351 mUserDataSeptetPadding = (headerSeptets * 7) - headerBits; 352 mMsgSeptetCount = userDataLength - headerSeptets; 353 } 354 mUserDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength 355 + 1; // Add the byte containing the length 356 } else { 357 mUserDataSeptetPadding = 0; 358 mMsgSeptetCount = userDataLength; 359 mUserDataMsgOffset = gsmSubmitGetTpUdOffset(); 360 } 361 if (V) { 362 Log.v(TAG, "encoding:" + mEncoding); 363 Log.v(TAG, "msgSeptetCount:" + mMsgSeptetCount); 364 Log.v(TAG, "userDataSeptetPadding:" + mUserDataSeptetPadding); 365 Log.v(TAG, "languageShiftTable:" + mLanguageShiftTable); 366 Log.v(TAG, "languageTable:" + mLanguageTable); 367 Log.v(TAG, "userDataMsgOffset:" + mUserDataMsgOffset); 368 } 369 } 370 371 private void gsmWriteDate(ByteArrayOutputStream header, long time) 372 throws UnsupportedEncodingException { 373 SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss"); 374 Date date = new Date(time); 375 String timeStr = format.format(date); // Format to YYMMDDTHHMMSS UTC time 376 if (V) { 377 Log.v(TAG, "Generated time string: " + timeStr); 378 } 379 byte[] timeChars = timeStr.getBytes("US-ASCII"); 380 381 for (int i = 0, n = timeStr.length(); i < n; i += 2) { 382 header.write((timeChars[i + 1] - 0x30) << 4 | (timeChars[i] 383 - 0x30)); // Offset from ascii char to decimal value 384 } 385 386 Calendar cal = Calendar.getInstance(); 387 int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (15 * 60 388 * 1000); /* offset in quarters of an hour */ 389 String offsetString; 390 if (offset < 0) { 391 offsetString = String.format("%1$02d", -(offset)); 392 char[] offsetChars = offsetString.toCharArray(); 393 header.write((offsetChars[1] - 0x30) << 4 | 0x40 | (offsetChars[0] - 0x30)); 394 } else { 395 offsetString = String.format("%1$02d", offset); 396 char[] offsetChars = offsetString.toCharArray(); 397 header.write((offsetChars[1] - 0x30) << 4 | (offsetChars[0] - 0x30)); 398 } 399 } 400 401 /* private void gsmSubmitExtractUserData() { 402 int userDataLength = data[gsmSubmitGetTpUdlOffset()]; 403 userData = new byte[userDataLength]; 404 System.arraycopy(userData, 0, data, gsmSubmitGetTpUdOffset(), userDataLength); 405 406 }*/ 407 408 /** 409 * Change the GSM Submit Pdu data in this object to a deliver PDU: 410 * - Build the new header with deliver PDU type, originator and time stamp. 411 * - Extract encoding details from the submit PDU 412 * - Extract user data length and user data from the submitPdu 413 * - Build the new PDU 414 * @param date the time stamp to include (The value is the number of milliseconds since 415 * Jan. 1, 1970 GMT.) 416 * @param originator the phone number to include in the deliver PDU header. Any undesired 417 * characters, 418 * such as '-' will be striped from this string. 419 */ 420 public void gsmChangeToDeliverPdu(long date, String originator) { 421 ByteArrayOutputStream newPdu = 422 new ByteArrayOutputStream(22); // 22 is the max length of the deliver pdu header 423 byte[] encodedAddress; 424 int userDataLength = 0; 425 try { 426 newPdu.write( 427 TP_MIT_DELIVER | TP_MMS_NO_MORE | TP_RP_NO_REPLY_PATH | TP_SRI_NO_REPORT 428 | (mData[0] & 0xff) & TP_UDHI_MASK); 429 encodedAddress = 430 PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(originator); 431 if (encodedAddress != null) { 432 int padding = 433 (encodedAddress[encodedAddress.length - 1] & 0xf0) == 0xf0 ? 1 : 0; 434 encodedAddress[0] = (byte) ((encodedAddress[0] - 1) * 2 435 - padding); // Convert from octet length to semi octet length 436 // Insert originator address into the header - this includes the length 437 newPdu.write(encodedAddress); 438 } else { 439 newPdu.write(0); /* zero length */ 440 newPdu.write(0x81); /* International type */ 441 } 442 443 newPdu.write(mData[gsmSubmitGetTpPidOffset()]); 444 newPdu.write(mData[gsmSubmitGetTpDcsOffset()]); 445 // Generate service center time stamp 446 gsmWriteDate(newPdu, date); 447 userDataLength = (mData[gsmSubmitGetTpUdlOffset()] & 0xff); 448 newPdu.write(userDataLength); 449 // Copy the pdu user data - keep in mind that the userDataLength is not the 450 // length in bytes for 7-bit encoding. 451 newPdu.write(mData, gsmSubmitGetTpUdOffset(), 452 mData.length - gsmSubmitGetTpUdOffset()); 453 } catch (IOException e) { 454 Log.e(TAG, "", e); 455 throw new IllegalArgumentException("Failed to change type to deliver PDU."); 456 } 457 mData = newPdu.toByteArray(); 458 } 459 460 /* SMS encoding to bmessage strings */ 461 462 /** get the encoding type as a bMessage string */ 463 public String getEncodingString() { 464 if (mType == SMS_TYPE_GSM) { 465 switch (mEncoding) { 466 case SmsMessage.ENCODING_7BIT: 467 if (mLanguageTable == 0) { 468 return "G-7BIT"; 469 } else { 470 return "G-7BITEXT"; 471 } 472 case SmsMessage.ENCODING_8BIT: 473 return "G-8BIT"; 474 case SmsMessage.ENCODING_16BIT: 475 return "G-16BIT"; 476 case SmsMessage.ENCODING_UNKNOWN: 477 default: 478 return ""; 479 } 480 } else /* SMS_TYPE_CDMA */ { 481 switch (mEncoding) { 482 case SmsMessage.ENCODING_7BIT: 483 return "C-7ASCII"; 484 case SmsMessage.ENCODING_8BIT: 485 return "C-8BIT"; 486 case SmsMessage.ENCODING_16BIT: 487 return "C-UNICODE"; 488 case SmsMessage.ENCODING_KSC5601: 489 return "C-KOREAN"; 490 case SmsMessage.ENCODING_UNKNOWN: 491 default: 492 return ""; 493 } 494 } 495 } 496 } 497 498 private static int sConcatenatedRef = new Random().nextInt(256); 499 500 protected static int getNextConcatenatedRef() { 501 sConcatenatedRef += 1; 502 return sConcatenatedRef; 503 } 504 505 public static ArrayList<SmsPdu> getSubmitPdus(String messageText, String address) { 506 /* Use the generic GSM/CDMA SMS Message functionality within Android to generate the 507 * SMS PDU's as once generated to send the SMS message. 508 */ 509 510 int activePhone = TelephonyManager.getDefault() 511 .getCurrentPhoneType(); // TODO: Change to use: ((TelephonyManager)myContext 512 // .getSystemService(Context.TELEPHONY_SERVICE)) 513 int phoneType; 514 GsmAlphabet.TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) 515 ? com.android.internal.telephony.cdma.SmsMessage.calculateLength( 516 (CharSequence) messageText, false, true) 517 : com.android.internal.telephony.gsm.SmsMessage.calculateLength( 518 (CharSequence) messageText, false); 519 520 SmsPdu newPdu; 521 String destinationAddress; 522 int msgCount = ted.msgCount; 523 int encoding; 524 int languageTable; 525 int languageShiftTable; 526 int refNumber = getNextConcatenatedRef() & 0x00FF; 527 ArrayList<String> smsFragments = SmsMessage.fragmentText(messageText); 528 ArrayList<SmsPdu> pdus = new ArrayList<SmsPdu>(msgCount); 529 byte[] data; 530 531 // Default to GSM, as this code should not be used, if we neither have CDMA not GSM. 532 phoneType = (activePhone == PHONE_TYPE_CDMA) ? SMS_TYPE_CDMA : SMS_TYPE_GSM; 533 encoding = ted.codeUnitSize; 534 languageTable = ted.languageTable; 535 languageShiftTable = ted.languageShiftTable; 536 destinationAddress = PhoneNumberUtils.stripSeparators(address); 537 if (destinationAddress == null || destinationAddress.length() < 2) { 538 destinationAddress = 539 "12"; // Ensure we add a number at least 2 digits as specified in the GSM spec. 540 } 541 542 if (msgCount == 1) { 543 data = SmsMessage.getSubmitPdu(null, destinationAddress, smsFragments.get(0), 544 false).encodedMessage; 545 newPdu = new SmsPdu(data, encoding, phoneType, languageTable); 546 pdus.add(newPdu); 547 } else { 548 /* This code is a reduced copy of the actual code used in the Android SMS sub system, 549 * hence the comments have been left untouched. */ 550 for (int i = 0; i < msgCount; i++) { 551 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 552 concatRef.refNumber = refNumber; 553 concatRef.seqNumber = i + 1; // 1-based sequence 554 concatRef.msgCount = msgCount; 555 // We currently set this to true since our messaging app will never 556 // send more than 255 parts (it converts the message to MMS well before that). 557 // However, we should support 3rd party messaging apps that might need 16-bit 558 // references 559 // Note: It's not sufficient to just flip this bit to true; it will have 560 // ripple effects (several calculations assume 8-bit ref). 561 concatRef.isEightBits = true; 562 SmsHeader smsHeader = new SmsHeader(); 563 smsHeader.concatRef = concatRef; 564 565 /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding 566 * will be determined(again) by getSubmitPdu(). 567 * All packets need to be encoded using the same encoding, as the bMessage 568 * only have one filed to describe the encoding for all messages in a concatenated 569 * SMS... */ 570 if (encoding == SmsConstants.ENCODING_7BIT) { 571 smsHeader.languageTable = languageTable; 572 smsHeader.languageShiftTable = languageShiftTable; 573 } 574 575 if (phoneType == SMS_TYPE_GSM) { 576 data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, 577 destinationAddress, smsFragments.get(i), false, 578 SmsHeader.toByteArray(smsHeader), encoding, languageTable, 579 languageShiftTable).encodedMessage; 580 } else { // SMS_TYPE_CDMA 581 UserData uData = new UserData(); 582 uData.payloadStr = smsFragments.get(i); 583 uData.userDataHeader = smsHeader; 584 if (encoding == SmsConstants.ENCODING_7BIT) { 585 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; 586 } else { // assume UTF-16 587 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 588 } 589 uData.msgEncodingSet = true; 590 data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( 591 destinationAddress, uData, false).encodedMessage; 592 } 593 newPdu = new SmsPdu(data, encoding, phoneType, languageTable); 594 pdus.add(newPdu); 595 } 596 } 597 598 return pdus; 599 } 600 601 /** 602 * Generate a list of deliver PDUs. The messageText and address parameters must be different 603 * from null, 604 * for CDMA the date can be omitted (and will be ignored if supplied) 605 * @param messageText The text to include. 606 * @param address The originator address. 607 * @param date The delivery time stamp. 608 * @return 609 */ 610 public static ArrayList<SmsPdu> getDeliverPdus(String messageText, String address, long date) { 611 ArrayList<SmsPdu> deliverPdus = getSubmitPdus(messageText, address); 612 613 /* 614 * For CDMA the only difference between deliver and submit pdus are the messageType, 615 * which is set in encodeMessageId, (the higher 4 bits of the 1st byte 616 * of the Message identification sub parameter data.) and the address type. 617 * 618 * For GSM, a larger part of the header needs to be generated. 619 */ 620 for (SmsPdu currentPdu : deliverPdus) { 621 if (currentPdu.getType() == SMS_TYPE_CDMA) { 622 currentPdu.cdmaChangeToDeliverPdu(date); 623 } else { /* SMS_TYPE_GSM */ 624 currentPdu.gsmChangeToDeliverPdu(date, address); 625 } 626 } 627 628 return deliverPdus; 629 } 630 631 632 /** 633 * The decoding only supports decoding the actual textual content of the PDU received 634 * from the MAP client. (As the Android system has no interface to send pre encoded PDUs) 635 * The destination address must be extracted from the bmessage vCard(s). 636 */ 637 public static String decodePdu(byte[] data, int type) { 638 String ret; 639 if (type == SMS_TYPE_CDMA) { 640 /* This is able to handle both submit and deliver PDUs */ 641 ret = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(0, data) 642 .getMessageBody(); 643 } else { 644 /* For GSM, there is no submit pdu decoder, and most parser utils are private, and 645 only minded for submit pdus */ 646 ret = gsmParseSubmitPdu(data); 647 } 648 return ret; 649 } 650 651 /* At the moment we do not support using a SC-address. Use this function to strip off 652 * the SC-address before parsing it to the SmsPdu. (this was added in errata 4335) 653 */ 654 private static byte[] gsmStripOffScAddress(byte[] data) { 655 /* The format of a native GSM SMS is: <sc-address><pdu> where sc-address is: 656 * <length-byte><type-byte><number-bytes> */ 657 int addressLength = data[0] & 0xff; // Treat the byte value as an unsigned value 658 // We could verify that the address-length is no longer than 11 bytes 659 if (addressLength >= data.length) { 660 throw new IllegalArgumentException( 661 "Length of address exeeds the length of the PDU data."); 662 } 663 int pduLength = data.length - (1 + addressLength); 664 byte[] newData = new byte[pduLength]; 665 System.arraycopy(data, 1 + addressLength, newData, 0, pduLength); 666 return newData; 667 } 668 669 private static String gsmParseSubmitPdu(byte[] data) { 670 /* Things to do: 671 * - extract hasUsrData bit 672 * - extract TP-DCS -> Character set, compressed etc. 673 * - extract user data header to get the language properties 674 * - extract user data 675 * - decode the string */ 676 //Strip off the SC-address before parsing 677 SmsPdu pdu = new SmsPdu(gsmStripOffScAddress(data), SMS_TYPE_GSM); 678 boolean userDataCompressed = false; 679 int dataCodingScheme = pdu.gsmSubmitGetTpDcs(); 680 int encodingType = SmsConstants.ENCODING_UNKNOWN; 681 String messageBody = null; 682 683 // Look up the data encoding scheme 684 if ((dataCodingScheme & 0x80) == 0) { 685 // Bits 7..4 == 0xxx 686 userDataCompressed = (0 != (dataCodingScheme & 0x20)); 687 688 if (userDataCompressed) { 689 Log.w(TAG, "4 - Unsupported SMS data coding scheme " + "(compression) " + ( 690 dataCodingScheme & 0xff)); 691 } else { 692 switch ((dataCodingScheme >> 2) & 0x3) { 693 case 0: // GSM 7 bit default alphabet 694 encodingType = SmsConstants.ENCODING_7BIT; 695 break; 696 697 case 2: // UCS 2 (16bit) 698 encodingType = SmsConstants.ENCODING_16BIT; 699 break; 700 701 case 1: // 8 bit data 702 case 3: // reserved 703 Log.w(TAG, "1 - Unsupported SMS data coding scheme " + (dataCodingScheme 704 & 0xff)); 705 encodingType = SmsConstants.ENCODING_8BIT; 706 break; 707 } 708 } 709 } else if ((dataCodingScheme & 0xf0) == 0xf0) { 710 userDataCompressed = false; 711 712 if (0 == (dataCodingScheme & 0x04)) { 713 // GSM 7 bit default alphabet 714 encodingType = SmsConstants.ENCODING_7BIT; 715 } else { 716 // 8 bit data 717 encodingType = SmsConstants.ENCODING_8BIT; 718 } 719 } else if ((dataCodingScheme & 0xF0) == 0xC0 || (dataCodingScheme & 0xF0) == 0xD0 720 || (dataCodingScheme & 0xF0) == 0xE0) { 721 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 722 723 // 0xC0 == 7 bit, don't store 724 // 0xD0 == 7 bit, store 725 // 0xE0 == UCS-2, store 726 727 if ((dataCodingScheme & 0xF0) == 0xE0) { 728 encodingType = SmsConstants.ENCODING_16BIT; 729 } else { 730 encodingType = SmsConstants.ENCODING_7BIT; 731 } 732 733 userDataCompressed = false; 734 735 // bit 0x04 reserved 736 } else if ((dataCodingScheme & 0xC0) == 0x80) { 737 // 3GPP TS 23.038 V7.0.0 (2006-03) section 4 738 // 0x80..0xBF == Reserved coding groups 739 if (dataCodingScheme == 0x84) { 740 // This value used for KSC5601 by carriers in Korea. 741 encodingType = SmsConstants.ENCODING_KSC5601; 742 } else { 743 Log.w(TAG, "5 - Unsupported SMS data coding scheme " + (dataCodingScheme & 0xff)); 744 } 745 } else { 746 Log.w(TAG, "3 - Unsupported SMS data coding scheme " + (dataCodingScheme & 0xff)); 747 } 748 749 pdu.setEncoding(encodingType); 750 pdu.gsmDecodeUserDataHeader(); 751 752 try { 753 switch (encodingType) { 754 case SmsConstants.ENCODING_UNKNOWN: 755 case SmsConstants.ENCODING_8BIT: 756 Log.w(TAG, "Unknown encoding type: " + encodingType); 757 messageBody = null; 758 break; 759 760 case SmsConstants.ENCODING_7BIT: 761 messageBody = GsmAlphabet.gsm7BitPackedToString(pdu.getData(), 762 pdu.getUserDataMsgOffset(), pdu.getMsgSeptetCount(), 763 pdu.getUserDataSeptetPadding(), pdu.getLanguageTable(), 764 pdu.getLanguageShiftTable()); 765 Log.i(TAG, "Decoded as 7BIT: " + messageBody); 766 767 break; 768 769 case SmsConstants.ENCODING_16BIT: 770 messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), 771 pdu.getUserDataMsgSize(), "utf-16"); 772 Log.i(TAG, "Decoded as 16BIT: " + messageBody); 773 break; 774 775 case SmsConstants.ENCODING_KSC5601: 776 messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), 777 pdu.getUserDataMsgSize(), "KSC5601"); 778 Log.i(TAG, "Decoded as KSC5601: " + messageBody); 779 break; 780 } 781 } catch (UnsupportedEncodingException e) { 782 Log.e(TAG, "Unsupported encoding type???", e); // This should never happen. 783 return null; 784 } 785 786 return messageBody; 787 } 788 789 } 790