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